Add lua bindings for override input hooks

custom
jacqueline 12 months ago
parent 920345b940
commit b255ea799e
  1. 4
      src/input/include/input_device.hpp
  2. 10
      src/input/include/input_hook.hpp
  3. 2
      src/input/include/input_nav_buttons.hpp
  4. 2
      src/input/include/input_touch_dpad.hpp
  5. 2
      src/input/include/input_touch_wheel.hpp
  6. 2
      src/input/include/input_volume_buttons.hpp
  7. 52
      src/input/include/lvgl_input_driver.hpp
  8. 19
      src/input/input_hook.cpp
  9. 3
      src/input/input_nav_buttons.cpp
  10. 3
      src/input/input_touch_dpad.cpp
  11. 3
      src/input/input_touch_wheel.cpp
  12. 3
      src/input/input_volume_buttons.cpp
  13. 137
      src/input/lvgl_input_driver.cpp

@ -29,7 +29,9 @@ class IInputDevice {
virtual auto read(lv_indev_data_t* data) -> void = 0; virtual auto read(lv_indev_data_t* data) -> void = 0;
virtual auto name() -> std::string = 0; virtual auto name() -> std::string = 0;
virtual auto hooks() -> std::vector<TriggerHooks> { return {}; } virtual auto triggers() -> std::vector<std::reference_wrapper<TriggerHooks>> {
return {};
}
}; };
} // namespace input } // namespace input

@ -32,6 +32,10 @@ class Hook {
auto name() const -> const std::string& { return name_; } auto name() const -> const std::string& { return name_; }
auto callback() -> std::optional<HookCallback>; auto callback() -> std::optional<HookCallback>;
// Not copyable or movable.
Hook(const Hook&) = delete;
Hook& operator=(const Hook&) = delete;
private: private:
std::string name_; std::string name_;
std::optional<HookCallback> default_; std::optional<HookCallback> default_;
@ -51,7 +55,11 @@ class TriggerHooks {
auto override(Trigger::State, std::optional<HookCallback>) -> void; auto override(Trigger::State, std::optional<HookCallback>) -> void;
auto name() const -> const std::string&; auto name() const -> const std::string&;
auto pushHooks(lua_State*) -> void; auto hooks() -> std::vector<std::reference_wrapper<Hook>>;
// Not copyable or movable.
TriggerHooks(const TriggerHooks&) = delete;
TriggerHooks& operator=(const TriggerHooks&) = delete;
private: private:
std::string name_; std::string name_;

@ -26,7 +26,7 @@ class NavButtons : public IInputDevice {
auto read(lv_indev_data_t* data) -> void override; auto read(lv_indev_data_t* data) -> void override;
auto name() -> std::string override; auto name() -> std::string override;
auto hooks() -> std::vector<TriggerHooks> override; auto triggers() -> std::vector<std::reference_wrapper<TriggerHooks>> override;
private: private:
drivers::IGpios& gpios_; drivers::IGpios& gpios_;

@ -25,7 +25,7 @@ class TouchDPad : public IInputDevice {
auto read(lv_indev_data_t* data) -> void override; auto read(lv_indev_data_t* data) -> void override;
auto name() -> std::string override; auto name() -> std::string override;
auto hooks() -> std::vector<TriggerHooks> override; auto triggers() -> std::vector<std::reference_wrapper<TriggerHooks>> override;
private: private:
drivers::TouchWheel& wheel_; drivers::TouchWheel& wheel_;

@ -28,7 +28,7 @@ class TouchWheel : public IInputDevice {
auto read(lv_indev_data_t* data) -> void override; auto read(lv_indev_data_t* data) -> void override;
auto name() -> std::string override; auto name() -> std::string override;
auto hooks() -> std::vector<TriggerHooks> override; auto triggers() -> std::vector<std::reference_wrapper<TriggerHooks>> override;
auto sensitivity() -> lua::Property&; auto sensitivity() -> lua::Property&;

@ -25,7 +25,7 @@ class VolumeButtons : public IInputDevice {
auto read(lv_indev_data_t* data) -> void override; auto read(lv_indev_data_t* data) -> void override;
auto name() -> std::string override; auto name() -> std::string override;
auto hooks() -> std::vector<TriggerHooks> override; auto triggers() -> std::vector<std::reference_wrapper<TriggerHooks>> override;
private: private:
drivers::IGpios& gpios_; drivers::IGpios& gpios_;

@ -18,6 +18,8 @@
#include "hal/lv_hal_indev.h" #include "hal/lv_hal_indev.h"
#include "input_device.hpp" #include "input_device.hpp"
#include "input_hook.hpp"
#include "lua_thread.hpp"
#include "nvs.hpp" #include "nvs.hpp"
#include "property.hpp" #include "property.hpp"
#include "touchwheel.hpp" #include "touchwheel.hpp"
@ -54,6 +56,56 @@ class LvglInputDriver {
std::vector<std::shared_ptr<IInputDevice>> inputs_; std::vector<std::shared_ptr<IInputDevice>> inputs_;
std::vector<std::shared_ptr<IFeedbackDevice>> feedbacks_; std::vector<std::shared_ptr<IFeedbackDevice>> 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<std::string, std::string> hooks_;
};
/* A hook override implemented as a lua callback */
struct LuaOverride {
lua_State* L;
int ref;
};
std::map<OverrideSelector, LuaOverride> overrides_;
auto setOverride(lua_State* L, const OverrideSelector&) -> void;
auto applyOverride(const OverrideSelector&, LuaOverride&) -> void;
bool is_locked_; bool is_locked_;
}; };

@ -85,23 +85,8 @@ auto TriggerHooks::name() const -> const std::string& {
return name_; return name_;
} }
auto TriggerHooks::pushHooks(lua_State* L) -> void { auto TriggerHooks::hooks() -> std::vector<std::reference_wrapper<Hook>> {
lua_newtable(L); return {click_, long_press_, repeat_};
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_);
} }
} // namespace input } // namespace input

@ -27,7 +27,8 @@ auto NavButtons::name() -> std::string {
return "buttons"; return "buttons";
} }
auto NavButtons::hooks() -> std::vector<TriggerHooks> { auto NavButtons::triggers()
-> std::vector<std::reference_wrapper<TriggerHooks>> {
return {up_, down_}; return {up_, down_};
} }

@ -55,7 +55,8 @@ auto TouchDPad::name() -> std::string {
return "dpad"; return "dpad";
} }
auto TouchDPad::hooks() -> std::vector<TriggerHooks> { auto TouchDPad::triggers()
-> std::vector<std::reference_wrapper<TriggerHooks>> {
return {centre_, up_, right_, down_, left_}; return {centre_, up_, right_, down_, left_};
} }

@ -92,7 +92,8 @@ auto TouchWheel::name() -> std::string {
return "wheel"; return "wheel";
} }
auto TouchWheel::hooks() -> std::vector<TriggerHooks> { auto TouchWheel::triggers()
-> std::vector<std::reference_wrapper<TriggerHooks>> {
return {centre_, up_, right_, down_, left_}; return {centre_, up_, right_, down_, left_};
} }

@ -25,7 +25,8 @@ auto VolumeButtons::name() -> std::string {
return "buttons"; return "buttons";
} }
auto VolumeButtons::hooks() -> std::vector<TriggerHooks> { auto VolumeButtons::triggers()
-> std::vector<std::reference_wrapper<TriggerHooks>> {
return {up_, down_}; return {up_, down_};
} }

@ -13,17 +13,22 @@
#include "device_factory.hpp" #include "device_factory.hpp"
#include "feedback_haptics.hpp" #include "feedback_haptics.hpp"
#include "input_hook.hpp"
#include "input_touch_wheel.hpp" #include "input_touch_wheel.hpp"
#include "input_trigger.hpp" #include "input_trigger.hpp"
#include "input_volume_buttons.hpp" #include "input_volume_buttons.hpp"
#include "lauxlib.h" #include "lauxlib.h"
#include "lua.h" #include "lua.h"
#include "lua_thread.hpp"
#include "lvgl.h" #include "lvgl.h"
#include "nvs.hpp" #include "nvs.hpp"
#include "property.hpp" #include "property.hpp"
[[maybe_unused]] static constexpr char kTag[] = "input"; [[maybe_unused]] static constexpr char kTag[] = "input";
static constexpr char kLuaTriggerMetatableName[] = "input_trigger";
static constexpr char kLuaOverrideText[] = "lua_callback";
namespace input { namespace input {
static void read_cb(lv_indev_drv_t* drv, lv_indev_data_t* data) { 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<LuaTrigger**>(
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 { 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); lua_newtable(L);
for (auto& dev : inputs_) { for (auto& dev : inputs_) {
lua_pushlstring(L, dev->name().data(), dev->name().size()); lua_pushlstring(L, dev->name().data(), dev->name().size());
lua_newtable(L); lua_newtable(L);
for (auto& hook : dev->hooks()) { for (auto& trigger : dev->triggers()) {
lua_pushlstring(L, hook.name().data(), hook.name().size()); lua_pushlstring(L, trigger.get().name().data(),
hook.pushHooks(L); trigger.get().name().size());
LuaTrigger** lua_obj = reinterpret_cast<LuaTrigger**>(
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);
} }
lua_rawset(L, -3); lua_rawset(L, -3);
} }
return 1; 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 } // namespace input

Loading…
Cancel
Save