mirror of
https://github.com/hyprwm/Hyprland.git
synced 2026-08-18 11:02:10 +00:00
372 lines
11 KiB
C++
372 lines
11 KiB
C++
#include <config/lua/LuaEventHandler.hpp>
|
|
|
|
#include <config/lua/objects/LuaEventSubscription.hpp>
|
|
#include <config/lua/objects/LuaKeybind.hpp>
|
|
#include <config/lua/objects/LuaWorkspaceRule.hpp>
|
|
|
|
#include <keybinds/Manager.hpp>
|
|
#include <event/EventBus.hpp>
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
#include <stdexcept>
|
|
|
|
extern "C" {
|
|
#include <lualib.h>
|
|
#include <lauxlib.h>
|
|
}
|
|
|
|
using namespace Config::Lua;
|
|
|
|
namespace {
|
|
Keybinds::CBind makeBind(std::vector<std::string> keys = {"Q"}, Keybinds::BindFlags flags = 0, Keybinds::SExtraBindArgs extra = {}) {
|
|
auto bind = Keybinds::CBind::make(std::move(keys), flags, [] { return Keybinds::SBindResult{}; }, std::move(extra));
|
|
if (!bind)
|
|
throw std::runtime_error(bind.error());
|
|
|
|
return std::move(*bind);
|
|
}
|
|
|
|
Keybinds::PBind makeBindPtr(std::vector<std::string> keys = {"Q"}, Keybinds::BindFlags flags = 0, Keybinds::SExtraBindArgs extra = {}) {
|
|
return makeShared<Keybinds::CBind>(makeBind(std::move(keys), flags, std::move(extra)));
|
|
}
|
|
|
|
class CScopedKeybindManager {
|
|
public:
|
|
CScopedKeybindManager() : m_previous(std::move(Keybinds::mgr())) {
|
|
Keybinds::mgr() = makeUnique<Keybinds::CKeybindManager>();
|
|
}
|
|
|
|
~CScopedKeybindManager() {
|
|
Keybinds::mgr() = std::move(m_previous);
|
|
}
|
|
|
|
private:
|
|
UP<Keybinds::CKeybindManager> m_previous;
|
|
};
|
|
|
|
class CLuaState {
|
|
public:
|
|
CLuaState() : m_lua(luaL_newstate()) {
|
|
luaL_openlibs(m_lua);
|
|
}
|
|
|
|
~CLuaState() {
|
|
if (m_lua)
|
|
lua_close(m_lua);
|
|
}
|
|
|
|
lua_State* get() const {
|
|
return m_lua;
|
|
}
|
|
|
|
private:
|
|
lua_State* m_lua = nullptr;
|
|
};
|
|
|
|
int getGlobalInt(lua_State* L, const char* name) {
|
|
lua_getglobal(L, name);
|
|
const int v = sc<int>(lua_tointeger(L, -1));
|
|
lua_pop(L, 1);
|
|
return v;
|
|
}
|
|
|
|
bool getGlobalBool(lua_State* L, const char* name) {
|
|
lua_getglobal(L, name);
|
|
const bool v = lua_toboolean(L, -1) != 0;
|
|
lua_pop(L, 1);
|
|
return v;
|
|
}
|
|
|
|
bool isGlobalNil(lua_State* L, const char* name) {
|
|
lua_getglobal(L, name);
|
|
const bool isnil = lua_isnil(L, -1);
|
|
lua_pop(L, 1);
|
|
return isnil;
|
|
}
|
|
}
|
|
|
|
TEST(ConfigLuaObjects, keybindCanToggleEnabledFromLua) {
|
|
CLuaState S;
|
|
const auto L = S.get();
|
|
|
|
Objects::CLuaKeybind{}.setup(L);
|
|
|
|
auto keybind = makeBindPtr();
|
|
|
|
Objects::CLuaKeybind::push(L, keybind);
|
|
lua_setglobal(L, "kb");
|
|
|
|
ASSERT_EQ(luaL_dostring(L, R"(
|
|
assert(kb:is_enabled() == true)
|
|
kb:set_enabled(false)
|
|
assert(kb:is_enabled() == false)
|
|
)"),
|
|
LUA_OK);
|
|
|
|
EXPECT_FALSE(keybind->enabled());
|
|
}
|
|
|
|
TEST(ConfigLuaObjects, workspaceRuleCanToggleEnabledFromLua) {
|
|
CLuaState S;
|
|
const auto L = S.get();
|
|
|
|
Objects::CLuaWorkspaceRule{}.setup(L);
|
|
|
|
auto rule = makeShared<Config::CWorkspaceRule>();
|
|
rule->setEnabled(true);
|
|
|
|
Objects::CLuaWorkspaceRule::push(L, rule);
|
|
lua_setglobal(L, "rule");
|
|
|
|
ASSERT_EQ(luaL_dostring(L, R"(
|
|
assert(rule:is_enabled() == true)
|
|
rule:set_enabled(false)
|
|
assert(rule:is_enabled() == false)
|
|
)"),
|
|
LUA_OK);
|
|
|
|
EXPECT_FALSE(rule->isEnabled());
|
|
}
|
|
|
|
TEST(ConfigLuaObjects, keybindExposesMetadataAndRemoveMethods) {
|
|
CScopedKeybindManager MANAGER;
|
|
CLuaState S;
|
|
const auto L = S.get();
|
|
|
|
Objects::CLuaKeybind{}.setup(L);
|
|
|
|
const auto FLAGS = sc<Keybinds::BindFlags>(Keybinds::BIND_FLAG_REPEAT | Keybinds::BIND_FLAG_LOCKED | Keybinds::BIND_FLAG_NON_CONSUMING | Keybinds::BIND_FLAG_DONT_INHIBIT |
|
|
Keybinds::BIND_FLAG_DEVICE_INCLUSIVE | Keybinds::BIND_FLAG_ALLOW_INPUT_CAPTURE);
|
|
const auto makeMetadataBind = [&] {
|
|
return makeBind({"SUPER", "Q"}, FLAGS,
|
|
{
|
|
.devices = {"kbd-a", "kbd-b"},
|
|
.metadata =
|
|
{
|
|
.displayKey = "SUPER + Q",
|
|
.description = "Close active window",
|
|
.handler = "HL.Dispatcher(close)",
|
|
.argument = "42",
|
|
.submap = "default",
|
|
},
|
|
});
|
|
};
|
|
|
|
const auto keybind = Keybinds::mgr()->addBind(makeMetadataBind());
|
|
const auto other = Keybinds::mgr()->addBind(makeMetadataBind());
|
|
|
|
Objects::CLuaKeybind::push(L, keybind);
|
|
lua_setglobal(L, "kb");
|
|
|
|
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 == "HL.Dispatcher(close)")
|
|
assert(kb.arg == "42")
|
|
assert(kb.modmask ~= nil)
|
|
assert(kb.key == "Q")
|
|
assert(kb.keycode == 0)
|
|
assert(kb.repeating == true)
|
|
assert(kb.locked == true)
|
|
assert(kb.non_consuming == true)
|
|
assert(kb.dont_inhibit == true)
|
|
assert(type(kb.devices) == "table")
|
|
assert(kb.allow_input_capture == true)
|
|
|
|
kb:remove()
|
|
kb:unbind()
|
|
)-"),
|
|
LUA_OK);
|
|
|
|
ASSERT_EQ(Keybinds::mgr()->registry().size(), 1);
|
|
EXPECT_EQ(Keybinds::mgr()->registry().binds().front(), other);
|
|
}
|
|
|
|
TEST(ConfigLuaObjects, keybindExposesKeycodeTrigger) {
|
|
CLuaState S;
|
|
const auto L = S.get();
|
|
|
|
Objects::CLuaKeybind{}.setup(L);
|
|
|
|
auto keybind = makeBindPtr({"code:24"});
|
|
Objects::CLuaKeybind::push(L, keybind);
|
|
lua_setglobal(L, "kb");
|
|
|
|
ASSERT_EQ(luaL_dostring(L, R"(
|
|
assert(kb.key == "")
|
|
assert(kb.keycode == 24)
|
|
)"),
|
|
LUA_OK);
|
|
}
|
|
|
|
TEST(ConfigLuaObjects, keybindRemovalDoesNotUnrefCallback) {
|
|
CScopedKeybindManager MANAGER;
|
|
CLuaState S;
|
|
const auto L = S.get();
|
|
|
|
Objects::CLuaKeybind{}.setup(L);
|
|
|
|
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 = HANDLER,
|
|
.argument = std::to_string(REF),
|
|
},
|
|
}));
|
|
Objects::CLuaKeybind::push(L, keybind);
|
|
lua_setglobal(L, "kb");
|
|
|
|
ASSERT_EQ(luaL_dostring(L, "kb:remove()"), LUA_OK);
|
|
|
|
lua_rawgeti(L, LUA_REGISTRYINDEX, REF);
|
|
EXPECT_TRUE(lua_isfunction(L, -1));
|
|
lua_pop(L, 1);
|
|
luaL_unref(L, LUA_REGISTRYINDEX, REF);
|
|
}
|
|
|
|
TEST(ConfigLuaObjects, objectsAreReadOnlyFromLua) {
|
|
CLuaState S;
|
|
const auto L = S.get();
|
|
|
|
Objects::CLuaKeybind{}.setup(L);
|
|
|
|
auto keybind = makeBindPtr();
|
|
Objects::CLuaKeybind::push(L, keybind);
|
|
lua_setglobal(L, "kb");
|
|
|
|
luaL_dostring(L, "kb.foo = 1");
|
|
luaL_dostring(L, "x = kb.foo");
|
|
EXPECT_TRUE(isGlobalNil(L, "x"));
|
|
}
|
|
|
|
TEST(ConfigLuaObjects, keybindSupportsEqAndToString) {
|
|
CLuaState S;
|
|
const auto L = S.get();
|
|
|
|
Objects::CLuaKeybind{}.setup(L);
|
|
|
|
auto keybindA = makeBindPtr();
|
|
auto keybindB = makeBindPtr();
|
|
|
|
Objects::CLuaKeybind::push(L, keybindA);
|
|
lua_setglobal(L, "kb1");
|
|
|
|
Objects::CLuaKeybind::push(L, keybindA);
|
|
lua_setglobal(L, "kb2");
|
|
|
|
Objects::CLuaKeybind::push(L, keybindB);
|
|
lua_setglobal(L, "kb3");
|
|
|
|
ASSERT_EQ(luaL_dostring(L, R"(
|
|
eq12 = (kb1 == kb2)
|
|
neq13 = (kb1 ~= kb3)
|
|
|
|
local s = tostring(kb1)
|
|
hasPrefix = type(s) == "string" and string.sub(s, 1, 11) == "HL.Keybind("
|
|
)"),
|
|
LUA_OK);
|
|
|
|
EXPECT_TRUE(getGlobalBool(L, "eq12"));
|
|
EXPECT_TRUE(getGlobalBool(L, "neq13"));
|
|
EXPECT_TRUE(getGlobalBool(L, "hasPrefix"));
|
|
}
|
|
|
|
TEST(ConfigLuaObjects, eventSubscriptionRemoveAndIsActive) {
|
|
CLuaState S;
|
|
const auto L = S.get();
|
|
|
|
Objects::CLuaEventSubscription{}.setup(L);
|
|
CLuaEventHandler handler(L);
|
|
|
|
ASSERT_EQ(luaL_dostring(L, R"(
|
|
count = 0
|
|
function onReload()
|
|
count = count + 1
|
|
end
|
|
)"),
|
|
LUA_OK);
|
|
|
|
lua_getglobal(L, "onReload");
|
|
ASSERT_TRUE(lua_isfunction(L, -1));
|
|
const int ref = luaL_ref(L, LUA_REGISTRYINDEX);
|
|
|
|
const auto handle = handler.registerEvent("config.reloaded", ref);
|
|
ASSERT_TRUE(handle.has_value());
|
|
|
|
Objects::CLuaEventSubscription::push(L, &handler, *handle);
|
|
lua_setglobal(L, "sub");
|
|
|
|
Event::bus()->m_events.config.reloaded.emit();
|
|
EXPECT_EQ(getGlobalInt(L, "count"), 1);
|
|
|
|
ASSERT_EQ(luaL_dostring(L, R"(
|
|
assert(sub:is_active() == true)
|
|
sub:remove()
|
|
assert(sub:is_active() == false)
|
|
sub:remove()
|
|
)"),
|
|
LUA_OK);
|
|
|
|
Event::bus()->m_events.config.reloaded.emit();
|
|
EXPECT_EQ(getGlobalInt(L, "count"), 1);
|
|
}
|
|
|
|
TEST(ConfigLuaObjects, eventSubscriptionSupportsEqAndToString) {
|
|
CLuaState S;
|
|
const auto L = S.get();
|
|
|
|
Objects::CLuaEventSubscription{}.setup(L);
|
|
CLuaEventHandler handler(L);
|
|
|
|
ASSERT_EQ(luaL_dostring(L, R"(
|
|
function cbA() end
|
|
function cbB() end
|
|
)"),
|
|
LUA_OK);
|
|
|
|
lua_getglobal(L, "cbA");
|
|
ASSERT_TRUE(lua_isfunction(L, -1));
|
|
const int refA = luaL_ref(L, LUA_REGISTRYINDEX);
|
|
|
|
lua_getglobal(L, "cbB");
|
|
ASSERT_TRUE(lua_isfunction(L, -1));
|
|
const int refB = luaL_ref(L, LUA_REGISTRYINDEX);
|
|
|
|
const auto handleA = handler.registerEvent("config.reloaded", refA);
|
|
const auto handleB = handler.registerEvent("config.reloaded", refB);
|
|
ASSERT_TRUE(handleA.has_value());
|
|
ASSERT_TRUE(handleB.has_value());
|
|
|
|
Objects::CLuaEventSubscription::push(L, &handler, *handleA);
|
|
lua_setglobal(L, "sub1");
|
|
|
|
Objects::CLuaEventSubscription::push(L, &handler, *handleA);
|
|
lua_setglobal(L, "sub2");
|
|
|
|
Objects::CLuaEventSubscription::push(L, &handler, *handleB);
|
|
lua_setglobal(L, "sub3");
|
|
|
|
ASSERT_EQ(luaL_dostring(L, R"(
|
|
eq12 = (sub1 == sub2)
|
|
neq13 = (sub1 ~= sub3)
|
|
|
|
local s = tostring(sub1)
|
|
hasPrefix = type(s) == "string" and string.sub(s, 1, 21) == "HL.EventSubscription("
|
|
)"),
|
|
LUA_OK);
|
|
|
|
EXPECT_TRUE(getGlobalBool(L, "eq12"));
|
|
EXPECT_TRUE(getGlobalBool(L, "neq13"));
|
|
EXPECT_TRUE(getGlobalBool(L, "hasPrefix"));
|
|
}
|