diff --git a/src/input/include/input_device.hpp b/src/input/include/input_device.hpp index 59456351..d944c3bf 100644 --- a/src/input/include/input_device.hpp +++ b/src/input/include/input_device.hpp @@ -29,7 +29,9 @@ class IInputDevice { virtual auto read(lv_indev_data_t* data) -> void = 0; virtual auto name() -> std::string = 0; - virtual auto hooks() -> std::vector { return {}; } + virtual auto triggers() -> std::vector> { + return {}; + } }; } // namespace input diff --git a/src/input/include/input_hook.hpp b/src/input/include/input_hook.hpp index 81eb80d9..a257c04a 100644 --- a/src/input/include/input_hook.hpp +++ b/src/input/include/input_hook.hpp @@ -32,6 +32,10 @@ class Hook { auto name() const -> const std::string& { return name_; } auto callback() -> std::optional; + // Not copyable or movable. + Hook(const Hook&) = delete; + Hook& operator=(const Hook&) = delete; + private: std::string name_; std::optional default_; @@ -51,7 +55,11 @@ class TriggerHooks { auto override(Trigger::State, std::optional) -> void; auto name() const -> const std::string&; - auto pushHooks(lua_State*) -> void; + auto hooks() -> std::vector>; + + // Not copyable or movable. + TriggerHooks(const TriggerHooks&) = delete; + TriggerHooks& operator=(const TriggerHooks&) = delete; private: std::string name_; diff --git a/src/input/include/input_nav_buttons.hpp b/src/input/include/input_nav_buttons.hpp index 4e4952c9..9feeb375 100644 --- a/src/input/include/input_nav_buttons.hpp +++ b/src/input/include/input_nav_buttons.hpp @@ -26,7 +26,7 @@ class NavButtons : public IInputDevice { auto read(lv_indev_data_t* data) -> void override; auto name() -> std::string override; - auto hooks() -> std::vector override; + auto triggers() -> std::vector> override; private: drivers::IGpios& gpios_; diff --git a/src/input/include/input_touch_dpad.hpp b/src/input/include/input_touch_dpad.hpp index 691e3243..0c45b2d9 100644 --- a/src/input/include/input_touch_dpad.hpp +++ b/src/input/include/input_touch_dpad.hpp @@ -25,7 +25,7 @@ class TouchDPad : public IInputDevice { auto read(lv_indev_data_t* data) -> void override; auto name() -> std::string override; - auto hooks() -> std::vector override; + auto triggers() -> std::vector> override; private: drivers::TouchWheel& wheel_; diff --git a/src/input/include/input_touch_wheel.hpp b/src/input/include/input_touch_wheel.hpp index 1f116da9..764cc68d 100644 --- a/src/input/include/input_touch_wheel.hpp +++ b/src/input/include/input_touch_wheel.hpp @@ -28,7 +28,7 @@ class TouchWheel : public IInputDevice { auto read(lv_indev_data_t* data) -> void override; auto name() -> std::string override; - auto hooks() -> std::vector override; + auto triggers() -> std::vector> override; auto sensitivity() -> lua::Property&; diff --git a/src/input/include/input_volume_buttons.hpp b/src/input/include/input_volume_buttons.hpp index a684aa48..e3246df4 100644 --- a/src/input/include/input_volume_buttons.hpp +++ b/src/input/include/input_volume_buttons.hpp @@ -25,7 +25,7 @@ class VolumeButtons : public IInputDevice { auto read(lv_indev_data_t* data) -> void override; auto name() -> std::string override; - auto hooks() -> std::vector override; + auto triggers() -> std::vector> override; private: drivers::IGpios& gpios_; diff --git a/src/input/include/lvgl_input_driver.hpp b/src/input/include/lvgl_input_driver.hpp index 9f43d27f..9adaf143 100644 --- a/src/input/include/lvgl_input_driver.hpp +++ b/src/input/include/lvgl_input_driver.hpp @@ -18,6 +18,8 @@ #include "hal/lv_hal_indev.h" #include "input_device.hpp" +#include "input_hook.hpp" +#include "lua_thread.hpp" #include "nvs.hpp" #include "property.hpp" #include "touchwheel.hpp" @@ -54,6 +56,56 @@ class LvglInputDriver { std::vector> inputs_; std::vector> feedbacks_; + /* + * Key for identifying which device, trigger, and specific hook are being + * overriden by Lua. + */ + struct OverrideSelector { + std::string device_name; + std::string trigger_name; + std::string hook_name; + + friend bool operator<(const OverrideSelector& l, + const OverrideSelector& r) { + return std::tie(l.device_name, l.trigger_name, l.hook_name) < + std::tie(r.device_name, r.trigger_name, r.hook_name); + } + }; + + /* Userdata object for tracking the Lua mirror of a TriggerHooks object. */ + class LuaTrigger { + public: + LuaTrigger(LvglInputDriver&, IInputDevice&, TriggerHooks&); + + static auto get(lua_State*, int idx) -> LuaTrigger&; + static auto luaGc(lua_State*) -> int; + static auto luaToString(lua_State*) -> int; + static auto luaNewIndex(lua_State*) -> int; + + static constexpr struct luaL_Reg kFuncs[] = {{"__gc", luaGc}, + {"__tostring", luaToString}, + {"__newindex", luaNewIndex}, + {NULL, NULL}}; + + private: + LvglInputDriver* driver_; + + std::string device_; + std::string trigger_; + std::map hooks_; + }; + + /* A hook override implemented as a lua callback */ + struct LuaOverride { + lua_State* L; + int ref; + }; + + std::map overrides_; + + auto setOverride(lua_State* L, const OverrideSelector&) -> void; + auto applyOverride(const OverrideSelector&, LuaOverride&) -> void; + bool is_locked_; }; diff --git a/src/input/input_hook.cpp b/src/input/input_hook.cpp index 1bb92196..48d6c2a4 100644 --- a/src/input/input_hook.cpp +++ b/src/input/input_hook.cpp @@ -85,23 +85,8 @@ auto TriggerHooks::name() const -> const std::string& { return name_; } -auto TriggerHooks::pushHooks(lua_State* L) -> void { - lua_newtable(L); - - auto add_trigger = [&](Hook& h) { - lua_pushlstring(L, h.name().data(), h.name().size()); - auto cb = h.callback(); - if (cb) { - lua_pushlstring(L, cb->name.data(), cb->name.size()); - } else { - lua_pushnil(L); - } - lua_rawset(L, -3); - }; - - add_trigger(click_); - add_trigger(long_press_); - add_trigger(repeat_); +auto TriggerHooks::hooks() -> std::vector> { + return {click_, long_press_, repeat_}; } } // namespace input diff --git a/src/input/input_nav_buttons.cpp b/src/input/input_nav_buttons.cpp index 7e579a16..522f8d6e 100644 --- a/src/input/input_nav_buttons.cpp +++ b/src/input/input_nav_buttons.cpp @@ -27,7 +27,8 @@ auto NavButtons::name() -> std::string { return "buttons"; } -auto NavButtons::hooks() -> std::vector { +auto NavButtons::triggers() + -> std::vector> { return {up_, down_}; } diff --git a/src/input/input_touch_dpad.cpp b/src/input/input_touch_dpad.cpp index f0805993..d8eff09b 100644 --- a/src/input/input_touch_dpad.cpp +++ b/src/input/input_touch_dpad.cpp @@ -55,7 +55,8 @@ auto TouchDPad::name() -> std::string { return "dpad"; } -auto TouchDPad::hooks() -> std::vector { +auto TouchDPad::triggers() + -> std::vector> { return {centre_, up_, right_, down_, left_}; } diff --git a/src/input/input_touch_wheel.cpp b/src/input/input_touch_wheel.cpp index 121b1ee5..67cab3bf 100644 --- a/src/input/input_touch_wheel.cpp +++ b/src/input/input_touch_wheel.cpp @@ -92,7 +92,8 @@ auto TouchWheel::name() -> std::string { return "wheel"; } -auto TouchWheel::hooks() -> std::vector { +auto TouchWheel::triggers() + -> std::vector> { return {centre_, up_, right_, down_, left_}; } diff --git a/src/input/input_volume_buttons.cpp b/src/input/input_volume_buttons.cpp index 607f81f1..37cf90e5 100644 --- a/src/input/input_volume_buttons.cpp +++ b/src/input/input_volume_buttons.cpp @@ -25,7 +25,8 @@ auto VolumeButtons::name() -> std::string { return "buttons"; } -auto VolumeButtons::hooks() -> std::vector { +auto VolumeButtons::triggers() + -> std::vector> { return {up_, down_}; } diff --git a/src/input/lvgl_input_driver.cpp b/src/input/lvgl_input_driver.cpp index 61a85fa5..a82b7438 100644 --- a/src/input/lvgl_input_driver.cpp +++ b/src/input/lvgl_input_driver.cpp @@ -13,17 +13,22 @@ #include "device_factory.hpp" #include "feedback_haptics.hpp" +#include "input_hook.hpp" #include "input_touch_wheel.hpp" #include "input_trigger.hpp" #include "input_volume_buttons.hpp" #include "lauxlib.h" #include "lua.h" +#include "lua_thread.hpp" #include "lvgl.h" #include "nvs.hpp" #include "property.hpp" [[maybe_unused]] static constexpr char kTag[] = "input"; +static constexpr char kLuaTriggerMetatableName[] = "input_trigger"; +static constexpr char kLuaOverrideText[] = "lua_callback"; + namespace input { static void read_cb(lv_indev_drv_t* drv, lv_indev_data_t* data) { @@ -106,22 +111,148 @@ auto LvglInputDriver::feedback(uint8_t event) -> void { } } +LvglInputDriver::LuaTrigger::LuaTrigger(LvglInputDriver& driver, + IInputDevice& dev, + TriggerHooks& trigger) + : driver_(&driver), device_(dev.name()), trigger_(trigger.name()) { + for (auto& hook : trigger.hooks()) { + auto cb = hook.get().callback(); + if (cb) { + hooks_[hook.get().name()] = hook.get().callback()->name; + } else { + hooks_[hook.get().name()] = ""; + } + } +} + +auto LvglInputDriver::LuaTrigger::get(lua_State* L, int idx) -> LuaTrigger& { + return **reinterpret_cast( + luaL_checkudata(L, idx, kLuaTriggerMetatableName)); +} + +auto LvglInputDriver::LuaTrigger::luaGc(lua_State* L) -> int { + LuaTrigger& trigger = LuaTrigger::get(L, 1); + delete &trigger; + return 0; +} + +auto LvglInputDriver::LuaTrigger::luaToString(lua_State* L) -> int { + LuaTrigger& trigger = LuaTrigger::get(L, 1); + std::stringstream out; + out << "{ "; + for (const auto& hook : trigger.hooks_) { + if (!hook.second.empty()) { + out << hook.first << "=" << hook.second << " "; + } + } + out << "}"; + lua_pushlstring(L, out.str().data(), out.str().size()); + return 1; +} + +auto LvglInputDriver::LuaTrigger::luaNewIndex(lua_State* L) -> int { + LuaTrigger& trigger = LuaTrigger::get(L, 1); + luaL_checktype(L, 3, LUA_TFUNCTION); + + size_t len = 0; + const char* str = luaL_checklstring(L, 2, &len); + if (!str) { + return 0; + } + OverrideSelector selector{ + .device_name = trigger.device_, + .trigger_name = trigger.trigger_, + .hook_name = std::string{str, len}, + }; + for (const auto& hook : trigger.hooks_) { + if (hook.first == selector.hook_name) { + trigger.driver_->setOverride(L, selector); + trigger.hooks_[hook.first] = kLuaOverrideText; + return 0; + } + } + return 0; +} + auto LvglInputDriver::pushHooks(lua_State* L) -> int { + if (luaL_getmetatable(L, kLuaTriggerMetatableName) == LUA_TNIL) { + luaL_newmetatable(L, kLuaTriggerMetatableName); + luaL_setfuncs(L, LuaTrigger::kFuncs, 0); + lua_pop(L, 1); + } + lua_pop(L, 1); + lua_newtable(L); for (auto& dev : inputs_) { lua_pushlstring(L, dev->name().data(), dev->name().size()); lua_newtable(L); - for (auto& hook : dev->hooks()) { - lua_pushlstring(L, hook.name().data(), hook.name().size()); - hook.pushHooks(L); + for (auto& trigger : dev->triggers()) { + lua_pushlstring(L, trigger.get().name().data(), + trigger.get().name().size()); + LuaTrigger** lua_obj = reinterpret_cast( + lua_newuserdatauv(L, sizeof(LuaTrigger*), 0)); + *lua_obj = new LuaTrigger(*this, *dev, trigger); + luaL_setmetatable(L, kLuaTriggerMetatableName); lua_rawset(L, -3); } lua_rawset(L, -3); } + return 1; } +auto LvglInputDriver::setOverride(lua_State* L, + const OverrideSelector& selector) -> void { + if (overrides_.contains(selector)) { + LuaOverride& prev = overrides_[selector]; + luaL_unref(prev.L, LUA_REGISTRYINDEX, prev.ref); + } + + int ref = luaL_ref(L, LUA_REGISTRYINDEX); + LuaOverride override{ + .L = L, + .ref = ref, + }; + overrides_[selector] = override; + applyOverride(selector, override); +} + +auto LvglInputDriver::applyOverride(const OverrideSelector& selector, + LuaOverride& override) -> void { + // In general, this algorithm is a very slow approach. We could do better + // by maintaing maps from [device|trigger|hook]_name to the relevant + // trigger, but in practice I expect maybe like 5 overrides total ever, + // spread across 2 devices with 2 or 5 hooks each. So it's not that big a + // deal. Don't worry about it!! + + // Look for a matching device. + for (auto& device : inputs_) { + if (device->name() != selector.device_name) { + continue; + } + // Look for a matching trigger + for (auto& trigger : device->triggers()) { + if (trigger.get().name() != selector.trigger_name) { + continue; + } + // Look for a matching hook + for (auto& hook : trigger.get().hooks()) { + if (hook.get().name() != selector.hook_name) { + continue; + } + // We found the target! Apply the override. + auto lua_callback = [=](lv_indev_data_t* d) { + lua_rawgeti(override.L, LUA_REGISTRYINDEX, override.ref); + lua::CallProtected(override.L, 0, 0); + }; + hook.get().override( + HookCallback{.name = kLuaOverrideText, .fn = lua_callback}); + } + } + } +} + } // namespace input