From 5ba33f846129f3e6c0505b19f94634b24505e828 Mon Sep 17 00:00:00 2001 From: Vaxry <43317083+vaxerski@users.noreply.github.com> Date: Sun, 26 Apr 2026 15:16:36 +0100 Subject: config/lua: init lua config manager, use lua if available (#13817) Adds a new lua config manager, which will automatically be used if there is a lua config present. --- src/config/lua/objects/LuaEventSubscription.cpp | 74 +++++ src/config/lua/objects/LuaEventSubscription.hpp | 12 + src/config/lua/objects/LuaKeybind.cpp | 180 +++++++++++ src/config/lua/objects/LuaKeybind.hpp | 12 + src/config/lua/objects/LuaLayerRule.cpp | 80 +++++ src/config/lua/objects/LuaLayerRule.hpp | 12 + src/config/lua/objects/LuaLayerSurface.cpp | 88 ++++++ src/config/lua/objects/LuaLayerSurface.hpp | 12 + src/config/lua/objects/LuaMonitor.cpp | 122 ++++++++ src/config/lua/objects/LuaMonitor.hpp | 12 + src/config/lua/objects/LuaNotification.cpp | 377 ++++++++++++++++++++++++ src/config/lua/objects/LuaNotification.hpp | 12 + src/config/lua/objects/LuaObjectHelpers.hpp | 51 ++++ src/config/lua/objects/LuaTimer.cpp | 109 +++++++ src/config/lua/objects/LuaTimer.hpp | 12 + src/config/lua/objects/LuaWindow.cpp | 277 +++++++++++++++++ src/config/lua/objects/LuaWindow.hpp | 12 + src/config/lua/objects/LuaWindowRule.cpp | 80 +++++ src/config/lua/objects/LuaWindowRule.hpp | 12 + src/config/lua/objects/LuaWorkspace.cpp | 87 ++++++ src/config/lua/objects/LuaWorkspace.hpp | 12 + 21 files changed, 1645 insertions(+) create mode 100644 src/config/lua/objects/LuaEventSubscription.cpp create mode 100644 src/config/lua/objects/LuaEventSubscription.hpp create mode 100644 src/config/lua/objects/LuaKeybind.cpp create mode 100644 src/config/lua/objects/LuaKeybind.hpp create mode 100644 src/config/lua/objects/LuaLayerRule.cpp create mode 100644 src/config/lua/objects/LuaLayerRule.hpp create mode 100644 src/config/lua/objects/LuaLayerSurface.cpp create mode 100644 src/config/lua/objects/LuaLayerSurface.hpp create mode 100644 src/config/lua/objects/LuaMonitor.cpp create mode 100644 src/config/lua/objects/LuaMonitor.hpp create mode 100644 src/config/lua/objects/LuaNotification.cpp create mode 100644 src/config/lua/objects/LuaNotification.hpp create mode 100644 src/config/lua/objects/LuaObjectHelpers.hpp create mode 100644 src/config/lua/objects/LuaTimer.cpp create mode 100644 src/config/lua/objects/LuaTimer.hpp create mode 100644 src/config/lua/objects/LuaWindow.cpp create mode 100644 src/config/lua/objects/LuaWindow.hpp create mode 100644 src/config/lua/objects/LuaWindowRule.cpp create mode 100644 src/config/lua/objects/LuaWindowRule.hpp create mode 100644 src/config/lua/objects/LuaWorkspace.cpp create mode 100644 src/config/lua/objects/LuaWorkspace.hpp (limited to 'src/config/lua/objects') diff --git a/src/config/lua/objects/LuaEventSubscription.cpp b/src/config/lua/objects/LuaEventSubscription.cpp new file mode 100644 index 00000000..c37491d7 --- /dev/null +++ b/src/config/lua/objects/LuaEventSubscription.cpp @@ -0,0 +1,74 @@ +#include "LuaEventSubscription.hpp" + +#include +#include + +using namespace Config::Lua; + +static constexpr const char* MT = "HL.EventSubscription"; + +namespace { + struct SEventSubscriptionRef { + CLuaEventHandler* handler = nullptr; + uint64_t handle = 0; + bool active = false; + }; +} + +static int eventSubscriptionEq(lua_State* L) { + const auto* lhs = sc(luaL_checkudata(L, 1, MT)); + const auto* rhs = sc(luaL_checkudata(L, 2, MT)); + + lua_pushboolean(L, lhs->handler == rhs->handler && lhs->handle == rhs->handle); + return 1; +} + +static int eventSubscriptionToString(lua_State* L) { + const auto* ref = sc(luaL_checkudata(L, 1, MT)); + + const auto str = std::format("HL.EventSubscription({},{})", ref->handle, ref->active ? "active" : "inactive"); + lua_pushstring(L, str.c_str()); + return 1; +} + +static int eventSubscriptionRemove(lua_State* L) { + auto* ref = sc(luaL_checkudata(L, 1, MT)); + + if (!ref->active || !ref->handler) + return 0; + + ref->handler->unregisterEvent(ref->handle); + ref->active = false; + return 0; +} + +static int eventSubscriptionIsActive(lua_State* L) { + auto* ref = sc(luaL_checkudata(L, 1, MT)); + + lua_pushboolean(L, ref->active); + return 1; +} + +static int eventSubscriptionIndex(lua_State* L) { + luaL_checkudata(L, 1, MT); + const std::string_view key = luaL_checkstring(L, 2); + + if (key == "remove") + lua_pushcfunction(L, eventSubscriptionRemove); + else if (key == "is_active") + lua_pushcfunction(L, eventSubscriptionIsActive); + else + lua_pushnil(L); + + return 1; +} + +void Objects::CLuaEventSubscription::setup(lua_State* L) { + registerMetatable(L, MT, eventSubscriptionIndex, gcRef, eventSubscriptionEq, eventSubscriptionToString); +} + +void Objects::CLuaEventSubscription::push(lua_State* L, CLuaEventHandler* handler, uint64_t handle) { + new (lua_newuserdata(L, sizeof(SEventSubscriptionRef))) SEventSubscriptionRef{.handler = handler, .handle = handle, .active = true}; + luaL_getmetatable(L, MT); + lua_setmetatable(L, -2); +} diff --git a/src/config/lua/objects/LuaEventSubscription.hpp b/src/config/lua/objects/LuaEventSubscription.hpp new file mode 100644 index 00000000..11cdfc62 --- /dev/null +++ b/src/config/lua/objects/LuaEventSubscription.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "LuaObjectHelpers.hpp" +#include "../LuaEventHandler.hpp" + +namespace Config::Lua::Objects { + class CLuaEventSubscription : public ILuaObjectWrapper { + public: + void setup(lua_State* L) override; + static void push(lua_State* L, CLuaEventHandler* handler, uint64_t handle); + }; +} diff --git a/src/config/lua/objects/LuaKeybind.cpp b/src/config/lua/objects/LuaKeybind.cpp new file mode 100644 index 00000000..aab2735d --- /dev/null +++ b/src/config/lua/objects/LuaKeybind.cpp @@ -0,0 +1,180 @@ +#include "LuaKeybind.hpp" + +#include +#include + +using namespace Config::Lua; + +static constexpr const char* MT = "HL.Keybind"; + +namespace { + std::optional> getKeybindFromUserdata(lua_State* L) { + auto* ref = sc*>(luaL_checkudata(L, 1, MT)); + return ref->lock(); + } + + void pushDeviceList(lua_State* L, const SKeybind& keybind) { + lua_newtable(L); + int i = 1; + for (const auto& device : keybind.devices) { + lua_pushstring(L, device.c_str()); + lua_rawseti(L, -2, i++); + } + } +} + +static int keybindEq(lua_State* L) { + const auto* lhs = sc*>(luaL_checkudata(L, 1, MT)); + const auto* rhs = sc*>(luaL_checkudata(L, 2, MT)); + + lua_pushboolean(L, lhs->lock() == rhs->lock()); + return 1; +} + +static int keybindToString(lua_State* L) { + const auto* ref = sc*>(luaL_checkudata(L, 1, MT)); + const auto keybind = ref->lock(); + + if (!keybind) + lua_pushstring(L, "HL.Keybind(expired)"); + else + lua_pushfstring(L, "HL.Keybind(%p)", keybind.get()); + + return 1; +} + +static int keybindSetEnabled(lua_State* L) { + luaL_checktype(L, 2, LUA_TBOOLEAN); + + const auto keybind = getKeybindFromUserdata(L); + if (!keybind) + return 0; + + (*keybind)->enabled = lua_toboolean(L, 2); + return 0; +} + +static int keybindIsEnabled(lua_State* L) { + const auto keybind = getKeybindFromUserdata(L); + if (!keybind) { + lua_pushnil(L); + return 1; + } + + lua_pushboolean(L, (*keybind)->enabled); + return 1; +} + +static int keybindRemove(lua_State* L) { + const auto keybind = getKeybindFromUserdata(L); + if (!keybind || !g_pKeybindManager) + return 0; + + if ((*keybind)->handler == "__lua") { + try { + const int ref = std::stoi((*keybind)->arg); + if (ref > 0) + luaL_unref(L, LUA_REGISTRYINDEX, ref); + } catch (...) { + // invalid ref, ignore + } + + (*keybind)->arg = std::to_string(LUA_NOREF); + } + + g_pKeybindManager->removeKeybind((*keybind)->modmask, SParsedKey{.key = (*keybind)->key, .keycode = (*keybind)->keycode, .catchAll = (*keybind)->catchAll}); + return 0; +} + +static int keybindGetDescription(lua_State* L) { + const auto keybind = getKeybindFromUserdata(L); + if (!keybind) { + lua_pushnil(L); + return 1; + } + + if (!(*keybind)->hasDescription) + lua_pushnil(L); + else + lua_pushstring(L, (*keybind)->description.c_str()); + + return 1; +} + +static int keybindIndex(lua_State* L) { + const auto keybind = getKeybindFromUserdata(L); + const std::string_view key = luaL_checkstring(L, 2); + + if (key == "set_enabled") + lua_pushcfunction(L, keybindSetEnabled); + else if (key == "is_enabled") + lua_pushcfunction(L, keybindIsEnabled); + else if (key == "remove" || key == "unbind") + lua_pushcfunction(L, keybindRemove); + else if (!keybind) + lua_pushnil(L); + else if (key == "enabled") + lua_pushboolean(L, (*keybind)->enabled); + else if (key == "has_description") + lua_pushboolean(L, (*keybind)->hasDescription); + else if (key == "description") + return keybindGetDescription(L); + else if (key == "display_key") + lua_pushstring(L, (*keybind)->displayKey.c_str()); + else if (key == "submap") + lua_pushstring(L, (*keybind)->submap.name.c_str()); + else if (key == "handler") + lua_pushstring(L, (*keybind)->handler.c_str()); + else if (key == "arg") + lua_pushstring(L, (*keybind)->arg.c_str()); + else if (key == "modmask") + lua_pushinteger(L, sc((*keybind)->modmask)); + else if (key == "key") + lua_pushstring(L, (*keybind)->key.c_str()); + else if (key == "keycode") + lua_pushinteger(L, sc((*keybind)->keycode)); + else if (key == "catchall") + lua_pushboolean(L, (*keybind)->catchAll); + else if (key == "repeating") + lua_pushboolean(L, (*keybind)->repeat); + else if (key == "locked") + lua_pushboolean(L, (*keybind)->locked); + else if (key == "release") + lua_pushboolean(L, (*keybind)->release); + else if (key == "non_consuming") + lua_pushboolean(L, (*keybind)->nonConsuming); + else if (key == "transparent") + lua_pushboolean(L, (*keybind)->transparent); + else if (key == "ignore_mods") + lua_pushboolean(L, (*keybind)->ignoreMods); + else if (key == "long_press") + lua_pushboolean(L, (*keybind)->longPress); + else if (key == "dont_inhibit") + lua_pushboolean(L, (*keybind)->dontInhibit); + else if (key == "click") + lua_pushboolean(L, (*keybind)->click); + else if (key == "drag") + lua_pushboolean(L, (*keybind)->drag); + else if (key == "submap_universal") + lua_pushboolean(L, (*keybind)->submapUniversal); + else if (key == "mouse") + lua_pushboolean(L, (*keybind)->mouse); + else if (key == "device_inclusive") + lua_pushboolean(L, (*keybind)->deviceInclusive); + else if (key == "devices") + pushDeviceList(L, **keybind); + else + lua_pushnil(L); + + return 1; +} + +void Objects::CLuaKeybind::setup(lua_State* L) { + registerMetatable(L, MT, keybindIndex, gcRef>, keybindEq, keybindToString); +} + +void Objects::CLuaKeybind::push(lua_State* L, const SP& keybind) { + new (lua_newuserdata(L, sizeof(WP))) WP(keybind); + luaL_getmetatable(L, MT); + lua_setmetatable(L, -2); +} diff --git a/src/config/lua/objects/LuaKeybind.hpp b/src/config/lua/objects/LuaKeybind.hpp new file mode 100644 index 00000000..c55ed998 --- /dev/null +++ b/src/config/lua/objects/LuaKeybind.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "LuaObjectHelpers.hpp" +#include "../../../managers/KeybindManager.hpp" + +namespace Config::Lua::Objects { + class CLuaKeybind : public ILuaObjectWrapper { + public: + void setup(lua_State* L) override; + static void push(lua_State* L, const SP& keybind); + }; +} diff --git a/src/config/lua/objects/LuaLayerRule.cpp b/src/config/lua/objects/LuaLayerRule.cpp new file mode 100644 index 00000000..22e06272 --- /dev/null +++ b/src/config/lua/objects/LuaLayerRule.cpp @@ -0,0 +1,80 @@ +#include "LuaLayerRule.hpp" + +#include "../../../desktop/rule/Engine.hpp" + +#include + +using namespace Config::Lua; + +static constexpr const char* MT = "HL.LayerRule"; + +// +static int layerRuleEq(lua_State* L) { + const auto* lhs = sc*>(luaL_checkudata(L, 1, MT)); + const auto* rhs = sc*>(luaL_checkudata(L, 2, MT)); + + lua_pushboolean(L, lhs->lock() == rhs->lock()); + return 1; +} + +static int layerRuleToString(lua_State* L) { + const auto* ref = sc*>(luaL_checkudata(L, 1, MT)); + const auto rule = ref->lock(); + + if (!rule) + lua_pushstring(L, "HL.LayerRule(expired)"); + else + lua_pushfstring(L, "HL.LayerRule(%p)", rule.get()); + + return 1; +} + +static int layerRuleSetEnabled(lua_State* L) { + auto* ref = sc*>(luaL_checkudata(L, 1, MT)); + luaL_checktype(L, 2, LUA_TBOOLEAN); + + const auto rule = ref->lock(); + if (!rule) + return 0; + + rule->setEnabled(lua_toboolean(L, 2)); + Desktop::Rule::ruleEngine()->updateAllRules(); + return 0; +} + +static int layerRuleIsEnabled(lua_State* L) { + auto* ref = sc*>(luaL_checkudata(L, 1, MT)); + + const auto rule = ref->lock(); + if (!rule) { + lua_pushnil(L); + return 1; + } + + lua_pushboolean(L, rule->isEnabled()); + return 1; +} + +static int layerRuleIndex(lua_State* L) { + luaL_checkudata(L, 1, MT); + const std::string_view key = luaL_checkstring(L, 2); + + if (key == "set_enabled") + lua_pushcfunction(L, layerRuleSetEnabled); + else if (key == "is_enabled") + lua_pushcfunction(L, layerRuleIsEnabled); + else + lua_pushnil(L); + + return 1; +} + +void Objects::CLuaLayerRule::setup(lua_State* L) { + registerMetatable(L, MT, layerRuleIndex, gcRef>, layerRuleEq, layerRuleToString); +} + +void Objects::CLuaLayerRule::push(lua_State* L, const SP& rule) { + new (lua_newuserdata(L, sizeof(WP))) WP(rule); + luaL_getmetatable(L, MT); + lua_setmetatable(L, -2); +} diff --git a/src/config/lua/objects/LuaLayerRule.hpp b/src/config/lua/objects/LuaLayerRule.hpp new file mode 100644 index 00000000..2e4d24e7 --- /dev/null +++ b/src/config/lua/objects/LuaLayerRule.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "LuaObjectHelpers.hpp" +#include "../../../desktop/rule/layerRule/LayerRule.hpp" + +namespace Config::Lua::Objects { + class CLuaLayerRule : public ILuaObjectWrapper { + public: + void setup(lua_State* L) override; + static void push(lua_State* L, const SP& rule); + }; +} diff --git a/src/config/lua/objects/LuaLayerSurface.cpp b/src/config/lua/objects/LuaLayerSurface.cpp new file mode 100644 index 00000000..99b22154 --- /dev/null +++ b/src/config/lua/objects/LuaLayerSurface.cpp @@ -0,0 +1,88 @@ +#include "LuaLayerSurface.hpp" +#include "LuaMonitor.hpp" +#include "LuaObjectHelpers.hpp" + +#include "../../../desktop/view/LayerSurface.hpp" + +#include +#include + +using namespace Config::Lua; + +static constexpr const char* MT = "HL.LayerSurface"; + +// +static int layerSurfaceEq(lua_State* L) { + const auto* lhs = sc(luaL_checkudata(L, 1, MT)); + const auto* rhs = sc(luaL_checkudata(L, 2, MT)); + + lua_pushboolean(L, lhs->lock() == rhs->lock()); + return 1; +} + +static int layerSurfaceToString(lua_State* L) { + const auto* ref = sc(luaL_checkudata(L, 1, MT)); + const auto ls = ref->lock(); + + if (!ls) + lua_pushstring(L, "HL.LayerSurface(expired)"); + else + lua_pushfstring(L, "HL.LayerSurface(%p)", ls.get()); + + return 1; +} + +static int layerSurfaceIndex(lua_State* L) { + auto* ref = sc(luaL_checkudata(L, 1, MT)); + const auto ls = ref->lock(); + if (!ls) { + Log::logger->log(Log::DEBUG, "[lua] Tried to access an expired object"); + lua_pushnil(L); + return 1; + } + + const std::string_view key = luaL_checkstring(L, 2); + + if (key == "address") + lua_pushstring(L, std::format("0x{:x}", reinterpret_cast(ls.get())).c_str()); + else if (key == "x") + lua_pushinteger(L, ls->m_geometry.x); + else if (key == "y") + lua_pushinteger(L, ls->m_geometry.y); + else if (key == "w") + lua_pushinteger(L, ls->m_geometry.width); + else if (key == "h") + lua_pushinteger(L, ls->m_geometry.height); + else if (key == "namespace") + lua_pushstring(L, ls->m_namespace.c_str()); + else if (key == "pid") + lua_pushinteger(L, sc(ls->getPID())); + else if (key == "monitor") { + const auto mon = ls->m_monitor.lock(); + if (mon) + Objects::CLuaMonitor::push(L, mon); + else + lua_pushnil(L); + } else if (key == "mapped") + lua_pushboolean(L, ls->m_mapped); + else if (key == "layer") + lua_pushinteger(L, sc(ls->m_layer)); + else if (key == "interactivity") + lua_pushinteger(L, sc(ls->m_interactivity)); + else if (key == "above_fullscreen") + lua_pushboolean(L, ls->m_aboveFullscreen); + else + lua_pushnil(L); + + return 1; +} + +void Objects::CLuaLayerSurface::setup(lua_State* L) { + registerMetatable(L, MT, layerSurfaceIndex, gcRef, layerSurfaceEq, layerSurfaceToString); +} + +void Objects::CLuaLayerSurface::push(lua_State* L, PHLLS ls) { + new (lua_newuserdata(L, sizeof(PHLLSREF))) PHLLSREF(ls ? ls->m_self : nullptr); + luaL_getmetatable(L, MT); + lua_setmetatable(L, -2); +} diff --git a/src/config/lua/objects/LuaLayerSurface.hpp b/src/config/lua/objects/LuaLayerSurface.hpp new file mode 100644 index 00000000..bfbd93e7 --- /dev/null +++ b/src/config/lua/objects/LuaLayerSurface.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "LuaObjectHelpers.hpp" +#include "../../../desktop/DesktopTypes.hpp" + +namespace Config::Lua::Objects { + class CLuaLayerSurface : public ILuaObjectWrapper { + public: + void setup(lua_State* L) override; + static void push(lua_State* L, PHLLS ls); + }; +} diff --git a/src/config/lua/objects/LuaMonitor.cpp b/src/config/lua/objects/LuaMonitor.cpp new file mode 100644 index 00000000..9d0701c1 --- /dev/null +++ b/src/config/lua/objects/LuaMonitor.cpp @@ -0,0 +1,122 @@ +#include "LuaMonitor.hpp" +#include "LuaWorkspace.hpp" +#include "LuaObjectHelpers.hpp" + +#include "../../../helpers/Monitor.hpp" +#include "../../../desktop/state/FocusState.hpp" + +#include + +using namespace Config::Lua; + +static constexpr const char* MT = "HL.Monitor"; + +// +static int monitorEq(lua_State* L) { + const auto* lhs = sc(luaL_checkudata(L, 1, MT)); + const auto* rhs = sc(luaL_checkudata(L, 2, MT)); + + lua_pushboolean(L, lhs->lock() == rhs->lock()); + return 1; +} + +static int monitorToString(lua_State* L) { + const auto* ref = sc(luaL_checkudata(L, 1, MT)); + const auto mon = ref->lock(); + + if (!mon) + lua_pushstring(L, "HL.Monitor(expired)"); + else + lua_pushfstring(L, "HL.Monitor(%d:%s)", mon->m_id, mon->m_name.c_str()); + + return 1; +} + +static int monitorIndex(lua_State* L) { + auto* ref = sc(luaL_checkudata(L, 1, MT)); + const auto mon = ref->lock(); + if (!mon) { + Log::logger->log(Log::DEBUG, "[lua] Tried to access an expired object"); + lua_pushnil(L); + return 1; + } + + const std::string_view key = luaL_checkstring(L, 2); + + if (key == "id") + lua_pushinteger(L, sc(mon->m_id)); + else if (key == "name") + lua_pushstring(L, mon->m_name.c_str()); + else if (key == "description") + lua_pushstring(L, mon->m_shortDescription.c_str()); + else if (key == "width") + lua_pushinteger(L, sc(mon->m_pixelSize.x)); + else if (key == "height") + lua_pushinteger(L, sc(mon->m_pixelSize.y)); + else if (key == "refresh_rate") + lua_pushnumber(L, mon->m_refreshRate); + else if (key == "x") + lua_pushinteger(L, sc(mon->m_position.x)); + else if (key == "y") + lua_pushinteger(L, sc(mon->m_position.y)); + else if (key == "active_workspace") { + if (mon->m_activeWorkspace) + Objects::CLuaWorkspace::push(L, mon->m_activeWorkspace); + else + lua_pushnil(L); + } else if (key == "active_special_workspace") { + if (mon->m_activeSpecialWorkspace) + Objects::CLuaWorkspace::push(L, mon->m_activeSpecialWorkspace); + else + lua_pushnil(L); + } else if (key == "position") { + lua_newtable(L); + lua_pushinteger(L, sc(mon->m_position.x)); + lua_setfield(L, -2, "x"); + lua_pushinteger(L, sc(mon->m_position.y)); + lua_setfield(L, -2, "y"); + } else if (key == "size") { + lua_newtable(L); + lua_pushinteger(L, sc(mon->m_pixelSize.x)); + lua_setfield(L, -2, "width"); + lua_pushinteger(L, sc(mon->m_pixelSize.y)); + lua_setfield(L, -2, "height"); + } else if (key == "scale") + lua_pushnumber(L, mon->m_scale); + else if (key == "transform") + lua_pushinteger(L, sc(mon->m_transform)); + else if (key == "dpms_status") + lua_pushboolean(L, mon->m_dpmsStatus); + else if (key == "vrr_active") + lua_pushboolean(L, mon->m_vrrActive); + else if (key == "is_mirror") + lua_pushboolean(L, mon->isMirror()); + else if (key == "mirrors") { + lua_newtable(L); + + int i = 1; + for (const auto& mirrorRef : mon->m_mirrors) { + const auto mirror = mirrorRef.lock(); + if (!mirror) + continue; + + Objects::CLuaMonitor::push(L, mirror); + lua_rawseti(L, -2, i++); + } + } else if (key == "focused") + lua_pushboolean(L, mon == Desktop::focusState()->monitor()); + else + lua_pushnil(L); + + return 1; +} + +void Objects::CLuaMonitor::setup(lua_State* L) { + registerMetatable(L, MT, monitorIndex, gcRef, monitorEq, monitorToString); +} + +void Objects::CLuaMonitor::push(lua_State* L, PHLMONITOR mon) { + new (lua_newuserdata(L, sizeof(PHLMONITORREF))) PHLMONITORREF(mon ? mon->m_self : nullptr); + luaL_getmetatable(L, MT); + lua_setmetatable(L, -2); +} diff --git a/src/config/lua/objects/LuaMonitor.hpp b/src/config/lua/objects/LuaMonitor.hpp new file mode 100644 index 00000000..585d51e8 --- /dev/null +++ b/src/config/lua/objects/LuaMonitor.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "LuaObjectHelpers.hpp" +#include "../../../desktop/DesktopTypes.hpp" + +namespace Config::Lua::Objects { + class CLuaMonitor : public ILuaObjectWrapper { + public: + void setup(lua_State* L) override; + static void push(lua_State* L, PHLMONITOR mon); + }; +} diff --git a/src/config/lua/objects/LuaNotification.cpp b/src/config/lua/objects/LuaNotification.cpp new file mode 100644 index 00000000..f116e2eb --- /dev/null +++ b/src/config/lua/objects/LuaNotification.cpp @@ -0,0 +1,377 @@ +#include "LuaNotification.hpp" + +#include "../../../helpers/MiscFunctions.hpp" + +#include +#include +#include +#include +#include + +using namespace Config::Lua; + +static constexpr const char* MT = "HL.Notification"; + +namespace { + struct SNotificationRef { + WP notification; + bool paused = false; + }; + + std::optional parseColor(lua_State* L, int idx) { + if (lua_isnumber(L, idx)) + return CHyprColor(sc(lua_tonumber(L, idx))); + + if (lua_isstring(L, idx)) { + auto parsed = configStringToInt(lua_tostring(L, idx)); + if (!parsed) + return std::nullopt; + + return CHyprColor(sc(*parsed)); + } + + return std::nullopt; + } + + std::optional iconFromString(std::string iconName) { + std::ranges::transform(iconName, iconName.begin(), [](const unsigned char c) { return std::tolower(c); }); + + static constexpr std::array, 10> ICON_NAMES = { + std::pair{"warning", ICON_WARNING}, std::pair{"warn", ICON_WARNING}, std::pair{"info", ICON_INFO}, std::pair{"hint", ICON_HINT}, + std::pair{"error", ICON_ERROR}, std::pair{"err", ICON_ERROR}, std::pair{"confused", ICON_CONFUSED}, std::pair{"question", ICON_CONFUSED}, + std::pair{"ok", ICON_OK}, std::pair{"none", ICON_NONE}, + }; + + for (const auto& [name, icon] : ICON_NAMES) { + if (name == iconName) + return icon; + } + + return std::nullopt; + } + + std::optional parseIcon(lua_State* L, int idx) { + if (lua_isnumber(L, idx)) { + const auto raw = sc(lua_tonumber(L, idx)); + if (raw >= ICON_WARNING && raw <= ICON_NONE) + return sc(raw); + + return std::nullopt; + } + + if (lua_isstring(L, idx)) + return iconFromString(lua_tostring(L, idx)); + + return std::nullopt; + } +} + +static int notificationEq(lua_State* L) { + const auto* lhs = sc(luaL_checkudata(L, 1, MT)); + const auto* rhs = sc(luaL_checkudata(L, 2, MT)); + + lua_pushboolean(L, lhs->notification.lock() == rhs->notification.lock()); + return 1; +} + +static int notificationToString(lua_State* L) { + const auto* ref = sc(luaL_checkudata(L, 1, MT)); + const auto notification = ref->notification.lock(); + + if (!notification) + lua_pushstring(L, "HL.Notification(expired)"); + else + lua_pushfstring(L, "HL.Notification(%p)", notification.get()); + + return 1; +} + +static int notificationGC(lua_State* L) { + auto* ref = sc(luaL_checkudata(L, 1, MT)); + + if (ref->paused) { + if (const auto notification = ref->notification.lock(); notification) + notification->unlock(); + } + + ref->~SNotificationRef(); + return 0; +} + +static int notificationPause(lua_State* L) { + auto* ref = sc(luaL_checkudata(L, 1, MT)); + + const auto notification = ref->notification.lock(); + if (!notification) + return 0; + + if (ref->paused) + return 0; + + notification->lock(); + ref->paused = true; + return 0; +} + +static int notificationResume(lua_State* L) { + auto* ref = sc(luaL_checkudata(L, 1, MT)); + + const auto notification = ref->notification.lock(); + if (!notification) + return 0; + + if (!ref->paused) + return 0; + + notification->unlock(); + ref->paused = false; + return 0; +} + +static int notificationSetPaused(lua_State* L) { + luaL_checkudata(L, 1, MT); + luaL_checktype(L, 2, LUA_TBOOLEAN); + + if (lua_toboolean(L, 2)) + return notificationPause(L); + + return notificationResume(L); +} + +static int notificationIsPaused(lua_State* L) { + auto* ref = sc(luaL_checkudata(L, 1, MT)); + + const auto notification = ref->notification.lock(); + if (!notification) { + lua_pushnil(L); + return 1; + } + + lua_pushboolean(L, notification->isLocked()); + return 1; +} + +static int notificationSetText(lua_State* L) { + auto* ref = sc(luaL_checkudata(L, 1, MT)); + const auto* text = luaL_checkstring(L, 2); + + if (const auto notification = ref->notification.lock(); notification) + notification->setText(text); + + return 0; +} + +static int notificationSetTimeout(lua_State* L) { + auto* ref = sc(luaL_checkudata(L, 1, MT)); + + const auto timeoutMs = sc(luaL_checknumber(L, 2)); + if (timeoutMs < 0.F) + return Config::Lua::Bindings::Internal::configError(L, "HL.Notification:set_timeout: timeout must be >= 0"); + + if (const auto notification = ref->notification.lock(); notification) + notification->resetTimeout(timeoutMs); + + return 0; +} + +static int notificationSetColor(lua_State* L) { + auto* ref = sc(luaL_checkudata(L, 1, MT)); + + const auto color = parseColor(L, 2); + if (!color) + return Config::Lua::Bindings::Internal::configError(L, "HL.Notification:set_color: expected a color string or number"); + + if (const auto notification = ref->notification.lock(); notification) + notification->setColor(*color); + + return 0; +} + +static int notificationSetIcon(lua_State* L) { + auto* ref = sc(luaL_checkudata(L, 1, MT)); + + const auto icon = parseIcon(L, 2); + if (!icon) + return Config::Lua::Bindings::Internal::configError(L, "HL.Notification:set_icon: expected an icon name or number"); + + if (const auto notification = ref->notification.lock(); notification) + notification->setIcon(*icon); + + return 0; +} + +static int notificationSetFontSize(lua_State* L) { + auto* ref = sc(luaL_checkudata(L, 1, MT)); + + const auto fontSize = sc(luaL_checknumber(L, 2)); + if (fontSize <= 0.F) + return Config::Lua::Bindings::Internal::configError(L, "HL.Notification:set_font_size: font size must be > 0"); + + if (const auto notification = ref->notification.lock(); notification) + notification->setFontSize(fontSize); + + return 0; +} + +static int notificationDismiss(lua_State* L) { + auto* ref = sc(luaL_checkudata(L, 1, MT)); + + if (const auto notification = ref->notification.lock(); notification) + Notification::overlay()->dismissNotification(notification); + + return 0; +} + +static int notificationGetText(lua_State* L) { + auto* ref = sc(luaL_checkudata(L, 1, MT)); + + const auto notification = ref->notification.lock(); + if (!notification) { + lua_pushnil(L); + return 1; + } + + lua_pushstring(L, notification->text().c_str()); + return 1; +} + +static int notificationGetTimeout(lua_State* L) { + auto* ref = sc(luaL_checkudata(L, 1, MT)); + + const auto notification = ref->notification.lock(); + if (!notification) { + lua_pushnil(L); + return 1; + } + + lua_pushnumber(L, notification->timeMs()); + return 1; +} + +static int notificationGetColor(lua_State* L) { + auto* ref = sc(luaL_checkudata(L, 1, MT)); + + const auto notification = ref->notification.lock(); + if (!notification) { + lua_pushnil(L); + return 1; + } + + lua_pushinteger(L, sc(notification->color().getAsHex())); + return 1; +} + +static int notificationGetIcon(lua_State* L) { + auto* ref = sc(luaL_checkudata(L, 1, MT)); + + const auto notification = ref->notification.lock(); + if (!notification) { + lua_pushnil(L); + return 1; + } + + lua_pushinteger(L, sc(notification->icon())); + return 1; +} + +static int notificationGetFontSize(lua_State* L) { + auto* ref = sc(luaL_checkudata(L, 1, MT)); + + const auto notification = ref->notification.lock(); + if (!notification) { + lua_pushnil(L); + return 1; + } + + lua_pushnumber(L, notification->fontSize()); + return 1; +} + +static int notificationGetElapsed(lua_State* L) { + auto* ref = sc(luaL_checkudata(L, 1, MT)); + + const auto notification = ref->notification.lock(); + if (!notification) { + lua_pushnil(L); + return 1; + } + + lua_pushnumber(L, notification->timeElapsedMs()); + return 1; +} + +static int notificationGetElapsedSinceCreation(lua_State* L) { + auto* ref = sc(luaL_checkudata(L, 1, MT)); + + const auto notification = ref->notification.lock(); + if (!notification) { + lua_pushnil(L); + return 1; + } + + lua_pushnumber(L, notification->timeElapsedSinceCreationMs()); + return 1; +} + +static int notificationIsAlive(lua_State* L) { + auto* ref = sc(luaL_checkudata(L, 1, MT)); + + lua_pushboolean(L, ref->notification.lock().get() != nullptr); + return 1; +} + +static int notificationIndex(lua_State* L) { + luaL_checkudata(L, 1, MT); + const std::string_view key = luaL_checkstring(L, 2); + + if (key == "pause") + lua_pushcfunction(L, notificationPause); + else if (key == "resume") + lua_pushcfunction(L, notificationResume); + else if (key == "set_paused") + lua_pushcfunction(L, notificationSetPaused); + else if (key == "is_paused") + lua_pushcfunction(L, notificationIsPaused); + else if (key == "set_text") + lua_pushcfunction(L, notificationSetText); + else if (key == "set_timeout") + lua_pushcfunction(L, notificationSetTimeout); + else if (key == "set_color") + lua_pushcfunction(L, notificationSetColor); + else if (key == "set_icon") + lua_pushcfunction(L, notificationSetIcon); + else if (key == "set_font_size") + lua_pushcfunction(L, notificationSetFontSize); + else if (key == "dismiss") + lua_pushcfunction(L, notificationDismiss); + else if (key == "get_text") + lua_pushcfunction(L, notificationGetText); + else if (key == "get_timeout") + lua_pushcfunction(L, notificationGetTimeout); + else if (key == "get_color") + lua_pushcfunction(L, notificationGetColor); + else if (key == "get_icon") + lua_pushcfunction(L, notificationGetIcon); + else if (key == "get_font_size") + lua_pushcfunction(L, notificationGetFontSize); + else if (key == "get_elapsed") + lua_pushcfunction(L, notificationGetElapsed); + else if (key == "get_elapsed_since_creation") + lua_pushcfunction(L, notificationGetElapsedSinceCreation); + else if (key == "is_alive") + lua_pushcfunction(L, notificationIsAlive); + else + lua_pushnil(L); + + return 1; +} + +void Objects::CLuaNotification::setup(lua_State* L) { + registerMetatable(L, MT, notificationIndex, notificationGC, notificationEq, notificationToString); +} + +void Objects::CLuaNotification::push(lua_State* L, const SP& notification) { + new (lua_newuserdata(L, sizeof(SNotificationRef))) SNotificationRef{.notification = WP(notification)}; + luaL_getmetatable(L, MT); + lua_setmetatable(L, -2); +} diff --git a/src/config/lua/objects/LuaNotification.hpp b/src/config/lua/objects/LuaNotification.hpp new file mode 100644 index 00000000..2ac8b0eb --- /dev/null +++ b/src/config/lua/objects/LuaNotification.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "LuaObjectHelpers.hpp" +#include "../../../notification/NotificationOverlay.hpp" + +namespace Config::Lua::Objects { + class CLuaNotification : public ILuaObjectWrapper { + public: + void setup(lua_State* L) override; + static void push(lua_State* L, const SP& notification); + }; +} diff --git a/src/config/lua/objects/LuaObjectHelpers.hpp b/src/config/lua/objects/LuaObjectHelpers.hpp new file mode 100644 index 00000000..69c284ef --- /dev/null +++ b/src/config/lua/objects/LuaObjectHelpers.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include "../bindings/LuaBindingsInternal.hpp" + +#include "../../../helpers/memory/Memory.hpp" + +extern "C" { +#include +} + +namespace Config::Lua::Objects { + + template + inline int gcRef(lua_State* L) { + sc(lua_touserdata(L, 1))->~T(); + return 0; + } + + inline int readOnlyNewIndex(lua_State* L) { + return Config::Lua::Bindings::Internal::configError(L, "attempt to modify read-only hl object"); + } + + inline void registerMetatable(lua_State* L, const char* name, lua_CFunction indexFn, lua_CFunction gcFn, lua_CFunction eqFn = nullptr, lua_CFunction toStringFn = nullptr) { + luaL_newmetatable(L, name); + lua_pushcfunction(L, indexFn); + lua_setfield(L, -2, "__index"); + lua_pushcfunction(L, readOnlyNewIndex); + lua_setfield(L, -2, "__newindex"); + lua_pushcfunction(L, gcFn); + lua_setfield(L, -2, "__gc"); + + if (eqFn) { + lua_pushcfunction(L, eqFn); + lua_setfield(L, -2, "__eq"); + } + + if (toStringFn) { + lua_pushcfunction(L, toStringFn); + lua_setfield(L, -2, "__tostring"); + } + + lua_pop(L, 1); + } + + class ILuaObjectWrapper { + public: + virtual ~ILuaObjectWrapper() = default; + virtual void setup(lua_State* L) = 0; + }; + +} diff --git a/src/config/lua/objects/LuaTimer.cpp b/src/config/lua/objects/LuaTimer.cpp new file mode 100644 index 00000000..46f9a519 --- /dev/null +++ b/src/config/lua/objects/LuaTimer.cpp @@ -0,0 +1,109 @@ +#include "LuaTimer.hpp" + +#include +#include + +using namespace Config::Lua; + +static constexpr const char* MT = "HL.Timer"; + +namespace { + struct STimerRef { + WP timer; + int timeoutMs = 0; + }; +} + +static int timerEq(lua_State* L) { + const auto* lhs = sc(luaL_checkudata(L, 1, MT)); + const auto* rhs = sc(luaL_checkudata(L, 2, MT)); + + lua_pushboolean(L, lhs->timer.lock() == rhs->timer.lock()); + return 1; +} + +static int timerToString(lua_State* L) { + const auto* ref = sc(luaL_checkudata(L, 1, MT)); + const auto timer = ref->timer.lock(); + + if (!timer) + lua_pushstring(L, "HL.Timer(expired)"); + else + lua_pushfstring(L, "HL.Timer(%p)", timer.get()); + + return 1; +} + +static int timerSetEnabled(lua_State* L) { + auto* ref = sc(luaL_checkudata(L, 1, MT)); + luaL_checktype(L, 2, LUA_TBOOLEAN); + + const auto timer = ref->timer.lock(); + if (!timer) + return 0; + + if (lua_toboolean(L, 2)) + timer->updateTimeout(std::chrono::milliseconds(std::max(ref->timeoutMs, 1))); + else + timer->updateTimeout(std::nullopt); + + return 0; +} + +static int timerSetTimeout(lua_State* L) { + auto* ref = sc(luaL_checkudata(L, 1, MT)); + luaL_checktype(L, 2, LUA_TNUMBER); + + const auto timer = ref->timer.lock(); + if (!timer) + return 0; + + uint64_t x = lua_tointeger(L, 2); + + if (x < 1) + return Config::Lua::Bindings::Internal::configError(L, "hl.timer:set_timeout: timeout must be greater or equal to 1ms"); + + timer->updateTimeout(std::chrono::milliseconds(x)); + ref->timeoutMs = x; + + return 0; +} + +static int timerIsEnabled(lua_State* L) { + auto* ref = sc(luaL_checkudata(L, 1, MT)); + + const auto timer = ref->timer.lock(); + if (!timer) { + lua_pushnil(L); + return 1; + } + + lua_pushboolean(L, timer->armed()); + return 1; +} + +static int timerIndex(lua_State* L) { + luaL_checkudata(L, 1, MT); + const std::string_view key = luaL_checkstring(L, 2); + + if (key == "set_enabled") + lua_pushcfunction(L, timerSetEnabled); + else if (key == "is_enabled") + lua_pushcfunction(L, timerIsEnabled); + else if (key == "set_timeout") + lua_pushcfunction(L, timerSetTimeout); + else + lua_pushnil(L); + + return 1; +} + +void Objects::CLuaTimer::setup(lua_State* L) { + registerMetatable(L, MT, timerIndex, gcRef, timerEq, timerToString); +} + +void Objects::CLuaTimer::push(lua_State* L, const SP& timer, int timeoutMs) { + new (lua_newuserdata(L, sizeof(STimerRef))) STimerRef{WP(timer), timeoutMs}; + luaL_getmetatable(L, MT); + lua_setmetatable(L, -2); +} diff --git a/src/config/lua/objects/LuaTimer.hpp b/src/config/lua/objects/LuaTimer.hpp new file mode 100644 index 00000000..0a602848 --- /dev/null +++ b/src/config/lua/objects/LuaTimer.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "LuaObjectHelpers.hpp" +#include "../../../managers/eventLoop/EventLoopTimer.hpp" + +namespace Config::Lua::Objects { + class CLuaTimer : public ILuaObjectWrapper { + public: + void setup(lua_State* L) override; + static void push(lua_State* L, const SP& timer, int timeoutMs); + }; +} diff --git a/src/config/lua/objects/LuaWindow.cpp b/src/config/lua/objects/LuaWindow.cpp new file mode 100644 index 00000000..bf332584 --- /dev/null +++ b/src/config/lua/objects/LuaWindow.cpp @@ -0,0 +1,277 @@ +#include "LuaWindow.hpp" +#include "LuaWorkspace.hpp" +#include "LuaMonitor.hpp" +#include "LuaObjectHelpers.hpp" + +#include "../../../desktop/view/Window.hpp" +#include "../../../desktop/view/Group.hpp" +#include "../../../desktop/Workspace.hpp" +#include "../../../desktop/state/FocusState.hpp" +#include "../../../desktop/history/WindowHistoryTracker.hpp" +#include "../../../layout/algorithm/Algorithm.hpp" +#include "../../../layout/algorithm/tiled/master/MasterAlgorithm.hpp" +#include "../../../layout/algorithm/tiled/scrolling/ScrollingAlgorithm.hpp" +#include "../../../layout/algorithm/tiled/dwindle/DwindleAlgorithm.hpp" +#include "../../../layout/space/Space.hpp" +#include "../../../layout/supplementary/WorkspaceAlgoMatcher.hpp" +#include "../../../managers/input/InputManager.hpp" + +#include +#include + +using namespace Config::Lua; + +static constexpr const char* MT = "HL.Window"; + +// +static int getFocusHistoryID(PHLWINDOW wnd) { + const auto& history = Desktop::History::windowTracker()->fullHistory(); + for (size_t i = 0; i < history.size(); ++i) { + if (history[i].lock() == wnd) + return sc(history.size() - i - 1); // reverse order for backwards compat + } + + return -1; +} + +static int windowEq(lua_State* L) { + const auto* lhs = sc(luaL_checkudata(L, 1, MT)); + const auto* rhs = sc(luaL_checkudata(L, 2, MT)); + + lua_pushboolean(L, lhs->lock() == rhs->lock()); + return 1; +} + +static int windowToString(lua_State* L) { + const auto* ref = sc(luaL_checkudata(L, 1, MT)); + const auto w = ref->lock(); + + if (!w) + lua_pushstring(L, "HL.Window(expired)"); + else + lua_pushfstring(L, "HL.Window(%p)", w.get()); + + return 1; +} + +static int windowIndex(lua_State* L) { + auto* ref = sc(luaL_checkudata(L, 1, MT)); + const auto w = ref->lock(); + if (!w) { + Log::logger->log(Log::DEBUG, "[lua] Tried to access an expired object"); + lua_pushnil(L); + return 1; + } + + const std::string_view key = luaL_checkstring(L, 2); + + if (key == "address") + lua_pushstring(L, std::format("0x{:x}", reinterpret_cast(w.get())).c_str()); + else if (key == "mapped") + lua_pushboolean(L, w->m_isMapped); + else if (key == "hidden") + lua_pushboolean(L, w->isHidden()); + else if (key == "at") { + lua_newtable(L); + lua_pushinteger(L, sc(w->m_realPosition->goal().x)); + lua_setfield(L, -2, "x"); + lua_pushinteger(L, sc(w->m_realPosition->goal().y)); + lua_setfield(L, -2, "y"); + } else if (key == "size") { + lua_newtable(L); + lua_pushinteger(L, sc(w->m_realSize->goal().x)); + lua_setfield(L, -2, "x"); + lua_pushinteger(L, sc(w->m_realSize->goal().y)); + lua_setfield(L, -2, "y"); + } else if (key == "workspace") { + if (w->m_workspace) + Objects::CLuaWorkspace::push(L, w->m_workspace); + else + lua_pushnil(L); + } else if (key == "floating") + lua_pushboolean(L, w->m_isFloating); + else if (key == "monitor") { + const auto mon = w->m_monitor.lock(); + if (mon) + Objects::CLuaMonitor::push(L, mon); + else + lua_pushnil(L); + } else if (key == "class") + lua_pushstring(L, w->m_class.c_str()); + else if (key == "title") + lua_pushstring(L, w->m_title.c_str()); + else if (key == "initial_class") + lua_pushstring(L, w->m_initialClass.c_str()); + else if (key == "initial_title") + lua_pushstring(L, w->m_initialTitle.c_str()); + else if (key == "pid") + lua_pushinteger(L, sc(w->getPID())); + else if (key == "xwayland") + lua_pushboolean(L, w->m_isX11); + else if (key == "pinned") + lua_pushboolean(L, w->m_pinned); + else if (key == "fullscreen") + lua_pushinteger(L, sc(sc(w->m_fullscreenState.internal))); + else if (key == "fullscreen_client") + lua_pushinteger(L, sc(sc(w->m_fullscreenState.client))); + else if (key == "over_fullscreen") + lua_pushboolean(L, w->m_createdOverFullscreen); + else if (key == "group") { + if (!w->m_group) { + lua_pushnil(L); + return 1; + } + + lua_newtable(L); + + lua_pushboolean(L, w->m_group->locked()); + lua_setfield(L, -2, "locked"); + + lua_pushboolean(L, w->m_group->denied()); + lua_setfield(L, -2, "denied"); + + lua_pushinteger(L, sc(w->m_group->size())); + lua_setfield(L, -2, "size"); + + lua_pushinteger(L, sc(w->m_group->getCurrentIdx()) + 1); + lua_setfield(L, -2, "current_index"); + + const auto current = w->m_group->current(); + if (current) + Objects::CLuaWindow::push(L, current); + else + lua_pushnil(L); + lua_setfield(L, -2, "current"); + + lua_newtable(L); + int i = 1; + for (const auto& grouped : w->m_group->windows()) { + const auto groupedWindow = grouped.lock(); + if (!groupedWindow) + continue; + + Objects::CLuaWindow::push(L, groupedWindow); + lua_rawseti(L, -2, i++); + } + lua_setfield(L, -2, "members"); + } else if (key == "tags") { + lua_newtable(L); + + int i = 1; + for (const auto& tag : w->m_ruleApplicator->m_tagKeeper.getTags()) { + lua_pushstring(L, tag.c_str()); + lua_rawseti(L, -2, i++); + } + } else if (key == "swallowing") { + const auto swallowed = w->m_swallowed.lock(); + if (swallowed) + Objects::CLuaWindow::push(L, swallowed); + else + lua_pushnil(L); + } else if (key == "focus_history_id") + lua_pushinteger(L, sc(getFocusHistoryID(w))); + else if (key == "inhibiting_idle") + lua_pushboolean(L, g_pInputManager && g_pInputManager->isWindowInhibiting(w, false)); + else if (key == "xdg_tag") { + const auto xdgTag = w->xdgTag(); + if (xdgTag) + lua_pushstring(L, xdgTag->c_str()); + else + lua_pushnil(L); + } else if (key == "xdg_description") { + const auto xdgDescription = w->xdgDescription(); + if (xdgDescription) + lua_pushstring(L, xdgDescription->c_str()); + else + lua_pushnil(L); + } else if (key == "content_type") + lua_pushstring(L, NContentType::toString(w->getContentType()).c_str()); + else if (key == "stable_id") + lua_pushinteger(L, sc(w->m_stableID)); + else if (key == "layout") { + const auto target = w->layoutTarget(); + if (!target || target->floating() || !w->m_workspace || !w->m_workspace->m_space) { + lua_pushnil(L); + return 1; + } + + const auto& algo = w->m_workspace->m_space->algorithm(); + if (!algo || !algo->tiledAlgo()) { + lua_pushnil(L); + return 1; + } + + const auto& tiledAlgo = algo->tiledAlgo(); + const std::string name = Layout::Supplementary::algoMatcher()->getNameForTiledAlgo(&typeid(*tiledAlgo.get())); + + lua_newtable(L); + lua_pushstring(L, name.c_str()); + lua_setfield(L, -2, "name"); + + if (const auto* master = dynamic_cast(tiledAlgo.get())) { + const auto node = master->getNodeFromTarget(target); + if (node) { + lua_pushboolean(L, node->isMaster); + lua_setfield(L, -2, "is_master"); + + lua_pushnumber(L, node->percMaster); + lua_setfield(L, -2, "perc_master"); + + lua_pushnumber(L, node->percSize); + lua_setfield(L, -2, "perc_size"); + } + } else if (auto* scrolling = dynamic_cast(tiledAlgo.get())) { + const auto data = scrolling->dataFor(target); + if (data) { + const auto col = data->column.lock(); + if (col) { + const auto scrollingData = col->scrollingData.lock(); + + lua_newtable(L); + + if (scrollingData) { + lua_pushinteger(L, sc(scrollingData->idx(col))); + lua_setfield(L, -2, "index"); + } + + lua_pushnumber(L, col->getColumnWidth()); + lua_setfield(L, -2, "width"); + + lua_newtable(L); + int i = 1; + for (const auto& td : col->targetDatas) { + const auto t = td->target.lock(); + if (t) { + const auto win = t->window(); + if (win) { + Objects::CLuaWindow::push(L, win); + lua_rawseti(L, -2, i++); + } + } + } + lua_setfield(L, -2, "windows"); + + lua_setfield(L, -2, "column"); + + lua_pushinteger(L, sc(col->idx(target))); + lua_setfield(L, -2, "index_in_column"); + } + } + } + } else if (key == "active") { + lua_pushboolean(L, w == Desktop::focusState()->window()); + } else + lua_pushnil(L); + + return 1; +} + +void Objects::CLuaWindow::setup(lua_State* L) { + registerMetatable(L, MT, windowIndex, gcRef, windowEq, windowToString); +} + +void Objects::CLuaWindow::push(lua_State* L, PHLWINDOW w) { + new (lua_newuserdata(L, sizeof(PHLWINDOWREF))) PHLWINDOWREF(w ? w->m_self : nullptr); + luaL_getmetatable(L, MT); + lua_setmetatable(L, -2); +} diff --git a/src/config/lua/objects/LuaWindow.hpp b/src/config/lua/objects/LuaWindow.hpp new file mode 100644 index 00000000..49f86149 --- /dev/null +++ b/src/config/lua/objects/LuaWindow.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "LuaObjectHelpers.hpp" +#include "../../../desktop/DesktopTypes.hpp" + +namespace Config::Lua::Objects { + class CLuaWindow : public ILuaObjectWrapper { + public: + void setup(lua_State* L) override; + static void push(lua_State* L, PHLWINDOW w); + }; +} diff --git a/src/config/lua/objects/LuaWindowRule.cpp b/src/config/lua/objects/LuaWindowRule.cpp new file mode 100644 index 00000000..63fd1a2c --- /dev/null +++ b/src/config/lua/objects/LuaWindowRule.cpp @@ -0,0 +1,80 @@ +#include "LuaWindowRule.hpp" + +#include "../../../desktop/rule/Engine.hpp" + +#include + +using namespace Config::Lua; + +static constexpr const char* MT = "HL.WindowRule"; + +// +static int windowRuleEq(lua_State* L) { + const auto* lhs = sc*>(luaL_checkudata(L, 1, MT)); + const auto* rhs = sc*>(luaL_checkudata(L, 2, MT)); + + lua_pushboolean(L, lhs->lock() == rhs->lock()); + return 1; +} + +static int windowRuleToString(lua_State* L) { + const auto* ref = sc*>(luaL_checkudata(L, 1, MT)); + const auto rule = ref->lock(); + + if (!rule) + lua_pushstring(L, "HL.WindowRule(expired)"); + else + lua_pushfstring(L, "HL.WindowRule(%p)", rule.get()); + + return 1; +} + +static int windowRuleSetEnabled(lua_State* L) { + auto* ref = sc*>(luaL_checkudata(L, 1, MT)); + luaL_checktype(L, 2, LUA_TBOOLEAN); + + const auto rule = ref->lock(); + if (!rule) + return 0; + + rule->setEnabled(lua_toboolean(L, 2)); + Desktop::Rule::ruleEngine()->updateAllRules(); + return 0; +} + +static int windowRuleIsEnabled(lua_State* L) { + auto* ref = sc*>(luaL_checkudata(L, 1, MT)); + + const auto rule = ref->lock(); + if (!rule) { + lua_pushnil(L); + return 1; + } + + lua_pushboolean(L, rule->isEnabled()); + return 1; +} + +static int windowRuleIndex(lua_State* L) { + luaL_checkudata(L, 1, MT); + const std::string_view key = luaL_checkstring(L, 2); + + if (key == "set_enabled") + lua_pushcfunction(L, windowRuleSetEnabled); + else if (key == "is_enabled") + lua_pushcfunction(L, windowRuleIsEnabled); + else + lua_pushnil(L); + + return 1; +} + +void Objects::CLuaWindowRule::setup(lua_State* L) { + registerMetatable(L, MT, windowRuleIndex, gcRef>, windowRuleEq, windowRuleToString); +} + +void Objects::CLuaWindowRule::push(lua_State* L, const SP& rule) { + new (lua_newuserdata(L, sizeof(WP))) WP(rule); + luaL_getmetatable(L, MT); + lua_setmetatable(L, -2); +} diff --git a/src/config/lua/objects/LuaWindowRule.hpp b/src/config/lua/objects/LuaWindowRule.hpp new file mode 100644 index 00000000..92d0651d --- /dev/null +++ b/src/config/lua/objects/LuaWindowRule.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "LuaObjectHelpers.hpp" +#include "../../../desktop/rule/windowRule/WindowRule.hpp" + +namespace Config::Lua::Objects { + class CLuaWindowRule : public ILuaObjectWrapper { + public: + void setup(lua_State* L) override; + static void push(lua_State* L, const SP& rule); + }; +} diff --git a/src/config/lua/objects/LuaWorkspace.cpp b/src/config/lua/objects/LuaWorkspace.cpp new file mode 100644 index 00000000..84e4df0b --- /dev/null +++ b/src/config/lua/objects/LuaWorkspace.cpp @@ -0,0 +1,87 @@ +#include "LuaWorkspace.hpp" +#include "LuaMonitor.hpp" +#include "LuaObjectHelpers.hpp" + +#include "../../../desktop/Workspace.hpp" +#include "../../../helpers/Monitor.hpp" + +#include + +using namespace Config::Lua; + +static constexpr const char* MT = "HL.Workspace"; + +// +static int workspaceEq(lua_State* L) { + const auto* lhs = sc(luaL_checkudata(L, 1, MT)); + const auto* rhs = sc(luaL_checkudata(L, 2, MT)); + + lua_pushboolean(L, lhs->lock() == rhs->lock()); + return 1; +} + +static int workspaceToString(lua_State* L) { + const auto* ref = sc(luaL_checkudata(L, 1, MT)); + const auto ws = ref->lock(); + + if (!ws || ws->inert()) + lua_pushstring(L, "HL.Workspace(expired)"); + else + lua_pushfstring(L, "HL.Workspace(%d:%s)", ws->m_id, ws->m_name.c_str()); + + return 1; +} + +static int workspaceIndex(lua_State* L) { + auto* ref = sc(luaL_checkudata(L, 1, MT)); + const auto ws = ref->lock(); + if (!ws || ws->inert()) { + Log::logger->log(Log::DEBUG, "[lua] Tried to access an expired object"); + lua_pushnil(L); + return 1; + } + + const std::string_view key = luaL_checkstring(L, 2); + + if (key == "id") + lua_pushinteger(L, sc(ws->m_id)); + else if (key == "name") + lua_pushstring(L, ws->m_name.c_str()); + else if (key == "monitor") { + const auto mon = ws->m_monitor.lock(); + if (mon) + Objects::CLuaMonitor::push(L, mon); + else + lua_pushnil(L); + } else if (key == "windows") + lua_pushinteger(L, sc(ws->getWindows())); + else if (key == "visible") + lua_pushboolean(L, ws->isVisible()); + else if (key == "special") + lua_pushboolean(L, ws->m_isSpecialWorkspace); + else if (key == "active") { + const auto mon = ws->m_monitor.lock(); + lua_pushboolean(L, mon && (mon->m_activeWorkspace == ws || mon->m_activeSpecialWorkspace == ws)); + } else if (key == "has_urgent") + lua_pushboolean(L, ws->hasUrgentWindow()); + else if (key == "fullscreen_mode") + lua_pushinteger(L, sc(ws->m_fullscreenMode)); + else if (key == "has_fullscreen") + lua_pushboolean(L, ws->m_hasFullscreenWindow); + else if (key == "is_persistent") + lua_pushboolean(L, ws->isPersistent()); + else + lua_pushnil(L); + + return 1; +} + +void Objects::CLuaWorkspace::setup(lua_State* L) { + registerMetatable(L, MT, workspaceIndex, gcRef, workspaceEq, workspaceToString); +} + +void Objects::CLuaWorkspace::push(lua_State* L, PHLWORKSPACE ws) { + new (lua_newuserdata(L, sizeof(PHLWORKSPACEREF))) PHLWORKSPACEREF(ws ? ws->m_self : nullptr); + luaL_getmetatable(L, MT); + lua_setmetatable(L, -2); +} diff --git a/src/config/lua/objects/LuaWorkspace.hpp b/src/config/lua/objects/LuaWorkspace.hpp new file mode 100644 index 00000000..db07feba --- /dev/null +++ b/src/config/lua/objects/LuaWorkspace.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "LuaObjectHelpers.hpp" +#include "../../../desktop/DesktopTypes.hpp" + +namespace Config::Lua::Objects { + class CLuaWorkspace : public ILuaObjectWrapper { + public: + void setup(lua_State* L) override; + static void push(lua_State* L, PHLWORKSPACE ws); + }; +} -- cgit v1.2.3