summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/config/lua/bindings/LuaBindingsDispatcherUtils.cpp144
-rw-r--r--src/config/lua/bindings/LuaBindingsDispatchers.cpp5
-rw-r--r--src/config/lua/bindings/LuaBindingsInternal.cpp5
-rw-r--r--src/config/lua/bindings/LuaBindingsInternal.hpp3
-rw-r--r--src/config/lua/bindings/LuaBindingsToplevel.cpp8
5 files changed, 155 insertions, 10 deletions
diff --git a/src/config/lua/bindings/LuaBindingsDispatcherUtils.cpp b/src/config/lua/bindings/LuaBindingsDispatcherUtils.cpp
new file mode 100644
index 00000000..a55dfc65
--- /dev/null
+++ b/src/config/lua/bindings/LuaBindingsDispatcherUtils.cpp
@@ -0,0 +1,144 @@
+#include "LuaBindingsInternal.hpp"
+
+using namespace Config::Lua::Bindings;
+
+static constexpr const char* DISPATCHER_MT = "HL.Dispatcher";
+static char DISPATCHER_TABLES_REGISTRY_KEY;
+
+namespace {
+ struct SDispatcherRef {
+ int ref = LUA_NOREF;
+ };
+}
+
+static int dispatcherGc(lua_State* L) {
+ auto* dispatcher = sc<SDispatcherRef*>(luaL_checkudata(L, 1, DISPATCHER_MT));
+ if (dispatcher->ref != LUA_NOREF) {
+ luaL_unref(L, LUA_REGISTRYINDEX, dispatcher->ref);
+ dispatcher->ref = LUA_NOREF;
+ }
+
+ return 0;
+}
+
+static int dispatcherCall(lua_State* L) {
+ return Internal::configError(L, "dispatcher objects cannot be called directly; use hl.dispatch(dispatcher)");
+}
+
+static int dispatcherToString(lua_State* L) {
+ lua_pushstring(L, "HL.Dispatcher");
+ return 1;
+}
+
+static void ensureDispatcherMetatable(lua_State* L) {
+ if (luaL_newmetatable(L, DISPATCHER_MT)) {
+ lua_pushcfunction(L, dispatcherGc);
+ lua_setfield(L, -2, "__gc");
+ lua_pushcfunction(L, dispatcherCall);
+ lua_setfield(L, -2, "__call");
+ lua_pushcfunction(L, dispatcherToString);
+ lua_setfield(L, -2, "__tostring");
+
+ lua_pushstring(L, DISPATCHER_MT);
+ lua_setfield(L, -2, "__metatable");
+ }
+
+ lua_pop(L, 1);
+}
+
+static bool isDispatcherTable(lua_State* L, int idx) {
+ if (!lua_istable(L, idx))
+ return false;
+
+ idx = lua_absindex(L, idx);
+ lua_pushlightuserdata(L, &DISPATCHER_TABLES_REGISTRY_KEY);
+ lua_rawget(L, LUA_REGISTRYINDEX);
+
+ if (!lua_istable(L, -1)) {
+ lua_pop(L, 1);
+ return false;
+ }
+
+ lua_pushvalue(L, idx);
+ lua_rawget(L, -2);
+ const bool result = lua_toboolean(L, -1);
+ lua_pop(L, 2);
+ return result;
+}
+
+static int dispatcherFactory(lua_State* L) {
+ const int nargs = lua_gettop(L);
+
+ lua_pushvalue(L, lua_upvalueindex(1));
+ lua_insert(L, 1);
+ lua_call(L, nargs, LUA_MULTRET);
+
+ const int nresults = lua_gettop(L);
+ if (nresults == 1 && lua_isfunction(L, -1))
+ return Internal::wrapDispatcher(L);
+
+ return nresults;
+}
+
+void Internal::setFn(lua_State* L, const char* name, lua_CFunction fn) {
+ if (isDispatcherTable(L, -1)) {
+ lua_pushcfunction(L, fn);
+ lua_pushcclosure(L, dispatcherFactory, 1);
+ } else
+ lua_pushcfunction(L, fn);
+
+ lua_setfield(L, -2, name);
+}
+
+void Internal::markDispatcherTable(lua_State* L) {
+ if (!lua_istable(L, -1))
+ return;
+
+ lua_pushlightuserdata(L, &DISPATCHER_TABLES_REGISTRY_KEY);
+ lua_rawget(L, LUA_REGISTRYINDEX);
+
+ if (!lua_istable(L, -1)) {
+ lua_pop(L, 1);
+ lua_newtable(L);
+ lua_pushlightuserdata(L, &DISPATCHER_TABLES_REGISTRY_KEY);
+ lua_pushvalue(L, -2);
+ lua_rawset(L, LUA_REGISTRYINDEX);
+ }
+
+ lua_pushvalue(L, -2);
+ lua_pushboolean(L, true);
+ lua_rawset(L, -3);
+ lua_pop(L, 1);
+}
+
+int Internal::wrapDispatcher(lua_State* L) {
+ luaL_checktype(L, -1, LUA_TFUNCTION);
+
+ const int ref = luaL_ref(L, LUA_REGISTRYINDEX);
+
+ new (lua_newuserdata(L, sizeof(SDispatcherRef))) SDispatcherRef{.ref = ref};
+
+ ensureDispatcherMetatable(L);
+ luaL_getmetatable(L, DISPATCHER_MT);
+ lua_setmetatable(L, -2);
+
+ return 1;
+}
+
+bool Internal::pushDispatcherFunction(lua_State* L, int idx) {
+ if (lua_isfunction(L, idx)) {
+ lua_pushvalue(L, idx);
+ return true;
+ }
+
+ auto* dispatcher = sc<SDispatcherRef*>(luaL_testudata(L, idx, DISPATCHER_MT));
+ if (!dispatcher || dispatcher->ref == LUA_NOREF)
+ return false;
+
+ lua_rawgeti(L, LUA_REGISTRYINDEX, dispatcher->ref);
+ if (lua_isfunction(L, -1))
+ return true;
+
+ lua_pop(L, 1);
+ return false;
+}
diff --git a/src/config/lua/bindings/LuaBindingsDispatchers.cpp b/src/config/lua/bindings/LuaBindingsDispatchers.cpp
index 4a981f4e..4fd332eb 100644
--- a/src/config/lua/bindings/LuaBindingsDispatchers.cpp
+++ b/src/config/lua/bindings/LuaBindingsDispatchers.cpp
@@ -1214,14 +1214,17 @@ static int hlWorkspaceSwapMonitors(lua_State* L) {
void Internal::registerDispatcherBindings(lua_State* L) {
lua_newtable(L);
+ Internal::markDispatcherTable(L);
{
lua_newtable(L);
+ Internal::markDispatcherTable(L);
Internal::setFn(L, "move_to_corner", hlCursorMoveToCorner);
Internal::setFn(L, "move", hlCursorMove);
lua_setfield(L, -2, "cursor");
lua_newtable(L);
+ Internal::markDispatcherTable(L);
Internal::setFn(L, "toggle", hlGroupToggle);
Internal::setFn(L, "next", hlGroupNext);
Internal::setFn(L, "prev", hlGroupPrev);
@@ -1232,6 +1235,7 @@ void Internal::registerDispatcherBindings(lua_State* L) {
lua_setfield(L, -2, "group");
lua_newtable(L);
+ Internal::markDispatcherTable(L);
Internal::setFn(L, "close", hlWindowClose);
Internal::setFn(L, "kill", hlWindowKill);
Internal::setFn(L, "signal", hlWindowSignal);
@@ -1255,6 +1259,7 @@ void Internal::registerDispatcherBindings(lua_State* L) {
lua_setfield(L, -2, "window");
lua_newtable(L);
+ Internal::markDispatcherTable(L);
Internal::setFn(L, "rename", hlWorkspaceRename);
Internal::setFn(L, "move", hlWorkspaceMove);
Internal::setFn(L, "swap_monitors", hlWorkspaceSwapMonitors);
diff --git a/src/config/lua/bindings/LuaBindingsInternal.cpp b/src/config/lua/bindings/LuaBindingsInternal.cpp
index e0cef146..5f195805 100644
--- a/src/config/lua/bindings/LuaBindingsInternal.cpp
+++ b/src/config/lua/bindings/LuaBindingsInternal.cpp
@@ -449,11 +449,6 @@ CA::eTogglableAction Internal::tableToggleAction(lua_State* L, int idx, const ch
return CA::TOGGLE_ACTION_TOGGLE;
}
-void Internal::setFn(lua_State* L, const char* name, lua_CFunction fn) {
- lua_pushcfunction(L, fn);
- lua_setfield(L, -2, name);
-}
-
void Internal::setMgrFn(lua_State* L, CConfigManager* mgr, const char* name, lua_CFunction fn) {
lua_pushlightuserdata(L, mgr);
lua_pushcclosure(L, fn, 1);
diff --git a/src/config/lua/bindings/LuaBindingsInternal.hpp b/src/config/lua/bindings/LuaBindingsInternal.hpp
index 024d7248..9b61360c 100644
--- a/src/config/lua/bindings/LuaBindingsInternal.hpp
+++ b/src/config/lua/bindings/LuaBindingsInternal.hpp
@@ -181,6 +181,9 @@ namespace Config::Lua::Bindings::Internal {
void setFn(lua_State* L, const char* name, lua_CFunction fn);
void setMgrFn(lua_State* L, CConfigManager* mgr, const char* name, lua_CFunction fn);
+ void markDispatcherTable(lua_State* L);
+ int wrapDispatcher(lua_State* L);
+ bool pushDispatcherFunction(lua_State* L, int idx);
template <typename T>
SParseError parseTableField(lua_State* L, int tableIdx, const char* field, T& parser) {
diff --git a/src/config/lua/bindings/LuaBindingsToplevel.cpp b/src/config/lua/bindings/LuaBindingsToplevel.cpp
index 2d54a7b3..1b345cde 100644
--- a/src/config/lua/bindings/LuaBindingsToplevel.cpp
+++ b/src/config/lua/bindings/LuaBindingsToplevel.cpp
@@ -132,13 +132,12 @@ static int hlBind(lua_State* L) {
if (auto res = parseKeyString(kb, keys); !res)
return Internal::configError(L, std::format("hl.bind: failed to parse key string: {}", res.error()));
- if (!lua_isfunction(L, 2))
+ if (!Internal::pushDispatcherFunction(L, 2))
return Internal::configError(L, "hl.bind: dispatcher must be a dispatcher (e.g. hl.dsp.window.close()) or a lua function");
if (kb.catchAll && mgr->m_currentSubmap.empty())
return Internal::configError(L, "hl.bind: catchall keybinds are only allowed in submaps.");
- lua_pushvalue(L, 2);
int ref = luaL_ref(L, LUA_REGISTRYINDEX);
kb.handler = "__lua";
kb.arg = std::to_string(ref);
@@ -293,10 +292,9 @@ static int hlExecCmd(lua_State* L) {
}
static int hlDispatch(lua_State* L) {
- if (!lua_isfunction(L, 1))
- return Internal::configError(L, "hl.dispatch: expected a dispatcher function (e.g. hl.dsp.window.close())");
+ if (!Internal::pushDispatcherFunction(L, 1))
+ return Internal::configError(L, "hl.dispatch: expected a dispatcher (e.g. hl.dsp.window.close())");
- lua_pushvalue(L, 1);
int status = LUA_OK;
if (auto* mgr = CConfigManager::fromLuaState(L); mgr)
status = mgr->guardedPCall(0, 1, 0, CConfigManager::LUA_TIMEOUT_DISPATCH_MS, "hl.dispatch");