diff --git a/hyprtester/src/tests/main/keybinds.cpp b/hyprtester/src/tests/main/keybinds.cpp index f4da9c518..872bf16ad 100644 --- a/hyprtester/src/tests/main/keybinds.cpp +++ b/hyprtester/src/tests/main/keybinds.cpp @@ -831,3 +831,17 @@ TEST_CASE(keybinds) { CALL_SUBTEST(perDeviceKeybind); CALL_SUBTEST(unbind); } + +TEST_CASE(luaDispatcherStrings) { + OK(getFromSocket("/eval B = hl.bind('SUPER + F24', hl.dsp.exec_cmd('true'))")); + EXPECT(getFromSocket("/repl return B.handler"), "HL.Dispatcher(exec_cmd)"); + OK(getFromSocket("/eval B:unbind()")); + + OK(getFromSocket("/eval B = hl.bind('SUPER + F24', hl.dsp.window.close())")); + EXPECT(getFromSocket("/repl return B.handler"), "HL.Dispatcher(close)"); + OK(getFromSocket("/eval B:unbind()")); + + OK(getFromSocket("/eval B = hl.bind('SUPER + F24', function() hl.exec_cmd('true') end)")); + EXPECT_STARTS_WITH(getFromSocket("/repl return B.handler"), "function: "); + OK(getFromSocket("/eval B:unbind()")); +} diff --git a/src/config/lua/bindings/LuaBindingsDispatcherUtils.cpp b/src/config/lua/bindings/LuaBindingsDispatcherUtils.cpp index a55dfc655..01d1a150e 100644 --- a/src/config/lua/bindings/LuaBindingsDispatcherUtils.cpp +++ b/src/config/lua/bindings/LuaBindingsDispatcherUtils.cpp @@ -7,7 +7,8 @@ static char DISPATCHER_TABLES_REGISTRY_KEY; namespace { struct SDispatcherRef { - int ref = LUA_NOREF; + int ref = LUA_NOREF; + int nameref = LUA_NOREF; }; } @@ -17,6 +18,10 @@ static int dispatcherGc(lua_State* L) { luaL_unref(L, LUA_REGISTRYINDEX, dispatcher->ref); dispatcher->ref = LUA_NOREF; } + if (dispatcher->nameref != LUA_NOREF) { + luaL_unref(L, LUA_REGISTRYINDEX, dispatcher->nameref); + dispatcher->nameref = LUA_NOREF; + } return 0; } @@ -26,7 +31,15 @@ static int dispatcherCall(lua_State* L) { } static int dispatcherToString(lua_State* L) { - lua_pushstring(L, "HL.Dispatcher"); + auto* dispatcher = sc(luaL_checkudata(L, 1, DISPATCHER_MT)); + std::string str; + if (dispatcher->nameref != LUA_NOREF) { + lua_rawgeti(L, LUA_REGISTRYINDEX, dispatcher->nameref); + str = lua_tostring(L, -1); + lua_pop(L, 1); + } else + str = "INVALID"; + lua_pushstring(L, std::format("HL.Dispatcher({})", str).c_str()); return 1; } @@ -74,8 +87,10 @@ static int dispatcherFactory(lua_State* L) { lua_call(L, nargs, LUA_MULTRET); const int nresults = lua_gettop(L); - if (nresults == 1 && lua_isfunction(L, -1)) + if (nresults == 1 && lua_isfunction(L, -1)) { + lua_pushvalue(L, lua_upvalueindex(2)); return Internal::wrapDispatcher(L); + } return nresults; } @@ -83,7 +98,8 @@ static int dispatcherFactory(lua_State* L) { 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); + lua_pushstring(L, name); + lua_pushcclosure(L, dispatcherFactory, 2); } else lua_pushcfunction(L, fn); @@ -112,11 +128,13 @@ void Internal::markDispatcherTable(lua_State* L) { } int Internal::wrapDispatcher(lua_State* L) { - luaL_checktype(L, -1, LUA_TFUNCTION); + luaL_checktype(L, -1, LUA_TSTRING); + const int nameref = luaL_ref(L, LUA_REGISTRYINDEX); + luaL_checktype(L, -1, LUA_TFUNCTION); const int ref = luaL_ref(L, LUA_REGISTRYINDEX); - new (lua_newuserdata(L, sizeof(SDispatcherRef))) SDispatcherRef{.ref = ref}; + new (lua_newuserdata(L, sizeof(SDispatcherRef))) SDispatcherRef{.ref = ref, .nameref = nameref}; ensureDispatcherMetatable(L); luaL_getmetatable(L, DISPATCHER_MT); diff --git a/src/config/lua/bindings/LuaBindingsToplevel.cpp b/src/config/lua/bindings/LuaBindingsToplevel.cpp index e6af667fe..20789460a 100644 --- a/src/config/lua/bindings/LuaBindingsToplevel.cpp +++ b/src/config/lua/bindings/LuaBindingsToplevel.cpp @@ -25,6 +25,7 @@ using namespace Hyprutils::String; extern "C" { #include +#include #include } @@ -79,6 +80,9 @@ static int hlBind(lua_State* L) { if (!keys) return Internal::configError(L, std::format("hl.bind: failed to parse key string: {}", keys.error())); + const std::string handler = luaL_tolstring(L, 2, nullptr); + lua_pop(L, 1); + 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"); @@ -94,7 +98,7 @@ static int hlBind(lua_State* L) { .metadata = { .displayKey = std::string{DISPLAY_KEYS}, - .handler = "__lua", + .handler = handler, .argument = std::to_string(LUA_REF->ref()), .submap = mgr->m_currentSubmap, .submapReset = mgr->m_currentSubmapReset, diff --git a/tests/config/lua/LuaObjectsBasic.cpp b/tests/config/lua/LuaObjectsBasic.cpp index f9835c754..731ec5e8a 100644 --- a/tests/config/lua/LuaObjectsBasic.cpp +++ b/tests/config/lua/LuaObjectsBasic.cpp @@ -146,8 +146,8 @@ TEST(ConfigLuaObjects, keybindExposesMetadataAndRemoveMethods) { { .displayKey = "SUPER + Q", .description = "Close active window", - .handler = "exec", - .argument = "kitty", + .handler = "HL.Dispatcher(close)", + .argument = "42", .submap = "default", }, }); @@ -159,13 +159,13 @@ TEST(ConfigLuaObjects, keybindExposesMetadataAndRemoveMethods) { Objects::CLuaKeybind::push(L, keybind); lua_setglobal(L, "kb"); - ASSERT_EQ(luaL_dostring(L, R"( + ASSERT_EQ(luaL_dostring(L, R"-( assert(kb.enabled == true) assert(kb.description == "Close active window") assert(kb.display_key == "SUPER + Q") assert(kb.submap == "default") - assert(kb.handler == "exec") - assert(kb.arg == "kitty") + assert(kb.handler == "HL.Dispatcher(close)") + assert(kb.arg == "42") assert(kb.modmask ~= nil) assert(kb.key == "Q") assert(kb.keycode == 0) @@ -178,7 +178,7 @@ TEST(ConfigLuaObjects, keybindExposesMetadataAndRemoveMethods) { kb:remove() kb:unbind() - )"), + )-"), LUA_OK); ASSERT_EQ(Keybinds::mgr()->registry().size(), 1); @@ -211,13 +211,15 @@ TEST(ConfigLuaObjects, keybindRemovalDoesNotUnrefCallback) { ASSERT_EQ(luaL_dostring(L, "return function() end"), LUA_OK); ASSERT_TRUE(lua_isfunction(L, -1)); + const std::string HANDLER = luaL_tolstring(L, -1, nullptr); + lua_pop(L, 1); const int REF = luaL_ref(L, LUA_REGISTRYINDEX); const auto keybind = Keybinds::mgr()->addBind(makeBind({"Q"}, 0, { .metadata = { - .handler = "__lua", + .handler = HANDLER, .argument = std::to_string(REF), }, }));