From c9ce88a457c9ed7124709a667d202a666f72bffa Mon Sep 17 00:00:00 2001 From: ailurux Date: Wed, 19 Mar 2025 04:25:20 +0000 Subject: [PATCH] ailurux/button-media-controls (#264) Splits the control scheme into separate schemes for the side buttons and touchwheel, allowing them to be configured independently of each other. At least one input must be used for navigation, and there are guards to prevent locking oneself out of any input. If the input scheme is invalid, a pop up will show alerting the user. Different behaviours can be bound to the side buttons for when the device is locked or unlocked. This PR also adds a mode for these buttons that controls media playback (prev/next on up/down, pressing both buttons toggles play/pause). There are some changes to the way inputs handle locking. Rather than the input devices tracking the current locked mode, different input devices are created on lock depending on the mode that is configured. Reviewed-on: https://codeberg.org/cool-tech-zone/tangara-fw/pulls/264 Co-authored-by: ailurux Co-committed-by: ailurux --- lua/main.lua | 2 +- lua/settings.lua | 29 +++++- lua/widgets.lua | 26 +++++ luals-stubs/controls.lua | 4 +- src/drivers/include/drivers/nvs.hpp | 27 +++-- src/drivers/nvs.cpp | 77 +++++++++----- src/tangara/input/device_factory.cpp | 60 +++++++++-- src/tangara/input/device_factory.hpp | 9 +- src/tangara/input/input_device.hpp | 2 +- src/tangara/input/input_hook_actions.cpp | 21 +++- src/tangara/input/input_hook_actions.hpp | 8 +- src/tangara/input/input_media_buttons.cpp | 62 +++++++++++ src/tangara/input/input_media_buttons.hpp | 43 ++++++++ src/tangara/input/input_nav_buttons.cpp | 2 +- src/tangara/input/input_nav_buttons.hpp | 2 +- src/tangara/input/input_touch_dpad.cpp | 2 +- src/tangara/input/input_touch_dpad.hpp | 2 +- src/tangara/input/input_touch_wheel.cpp | 2 +- src/tangara/input/input_touch_wheel.hpp | 2 +- src/tangara/input/input_volume_buttons.cpp | 10 +- src/tangara/input/input_volume_buttons.hpp | 5 +- src/tangara/input/lvgl_input_driver.cpp | 114 +++++++++++++-------- src/tangara/input/lvgl_input_driver.hpp | 14 +-- src/tangara/lua/lua_controls.cpp | 50 +++++++-- src/tangara/ui/ui_fsm.cpp | 3 +- 25 files changed, 443 insertions(+), 135 deletions(-) create mode 100644 src/tangara/input/input_media_buttons.cpp create mode 100644 src/tangara/input/input_media_buttons.hpp diff --git a/lua/main.lua b/lua/main.lua index 12705c00..5be79c9a 100644 --- a/lua/main.lua +++ b/lua/main.lua @@ -74,7 +74,7 @@ local function init_ui() end end end), - controls.scheme:bind(function() + controls.wheel_scheme:bind(function() -- Set up a shortcut for jumping straight to the 'now playing' screen. -- Implemented as a binding so that the shortcut is still applied even if -- the control scheme is changed at runtime. diff --git a/lua/settings.lua b/lua/settings.lua index 5ed93521..d60ecfda 100644 --- a/lua/settings.lua +++ b/lua/settings.lua @@ -471,20 +471,33 @@ settings.InputSettings = SettingsScreen:new { controls_chooser:onevent(lvgl.EVENT.VALUE_CHANGED, function() local option = controls_chooser:get('selected') local scheme = option_to_scheme[option] - control_scheme:set(scheme) + local prev_scheme = control_scheme:get() + -- Check the new scheme is valid + if not control_scheme:set(scheme) then + widgets.PopUp("Controls not valid") + control_scheme:set(prev_scheme) + end end) return controls_chooser end theme.set_subject(self.content:Label { - text = "Control scheme", + text = "Wheel Controls", }, "settings_title") - local controls_chooser = make_scheme_control(self, controls.schemes(), controls.scheme) + local controls_chooser = make_scheme_control(self, controls.wheel_schemes(), controls.wheel_scheme) local controls_chooser_desc = widgets.Description(controls_chooser, "Control scheme") theme.set_subject(self.content:Label { - text = "Control scheme when locked", + text = "Side Button Controls", + }, "settings_title") + make_scheme_control(self, controls.button_schemes(), controls.button_scheme) + + theme.set_subject(self.content:Label { + text = "Side Button Controls When Locked", + w = lvgl.PCT(80), + h = lvgl.SIZE_CONTENT, + long_mode = lvgl.LABEL.LONG_WRAP, }, "settings_title") local controls_locked = make_scheme_control(self, controls.locked_schemes(), controls.locked_scheme) local controls_locked_desc = widgets.Description(controls_locked, "Control scheme when locked") @@ -510,6 +523,14 @@ settings.InputSettings = SettingsScreen:new { controls.scroll_sensitivity:set(sensitivity:value() * slider_scale) end) local sensitivity_desc = widgets.Description(sensitivity, "Scroll Sensitivity") + + local spacer = self.content:Object { + w = lvgl.PCT(90), + h = 10, + } + sensitivity:onevent(lvgl.EVENT.FOCUSED, function() + spacer:scroll_to_view(true) + end) end } diff --git a/lua/widgets.lua b/lua/widgets.lua index de2aa43d..5e2ed858 100644 --- a/lua/widgets.lua +++ b/lua/widgets.lua @@ -372,4 +372,30 @@ function widgets.InfiniteList(parent, iterator, opts) return infinite_list end +function widgets.PopUp(text) + require("alerts").show(function() + local container = lvgl.Object(nil, { + w = lvgl.PCT(80), + h = lvgl.SIZE_CONTENT, + flex = { + flex_direction = "column", + justify_content = "center", + align_items = "center", + align_content = "center", + }, + radius = 8, + pad_all = 5, + }) + theme.set_subject(container, "pop_up") + container:Label { + text = text, + text_font = font.fusion_10, + w = lvgl.PCT(100), + h = lvgl.SIZE_CONTENT, + long_mode = lvgl.LABEL.LONG_WRAP, + } + container:center() + end) +end + return widgets diff --git a/luals-stubs/controls.lua b/luals-stubs/controls.lua index a95bc64f..0a8f4baa 100644 --- a/luals-stubs/controls.lua +++ b/luals-stubs/controls.lua @@ -8,7 +8,9 @@ --- controls. These controls include the touchwheel, the lock switch, and the --- side buttons. --- @class controls ---- @field scheme Property The currently configured control scheme +--- @field wheel_scheme Property The currently configured control scheme for the touchwheel +--- @field button_scheme Property The currently configured control scheme for the side buttons +--- @field locked_scheme Property The currently configured control scheme for the side buttons when locked --- @field scroll_sensitivity Property How much rotational motion is required on the touchwheel per scroll tick. --- @field lock_switch Property The current state of the device's lock switch. --- @field hooks funtion Returns a table containing the inputs and actions associated with the current control scheme. diff --git a/src/drivers/include/drivers/nvs.hpp b/src/drivers/include/drivers/nvs.hpp index 7ef1fbf7..b490ac3d 100644 --- a/src/drivers/include/drivers/nvs.hpp +++ b/src/drivers/include/drivers/nvs.hpp @@ -143,23 +143,27 @@ class NvsStorage { auto AmpLeftBias() -> int_fast8_t; auto AmpLeftBias(int_fast8_t) -> void; - enum class InputModes : uint8_t { - kButtonsOnly = 0, - kButtonsWithWheel = 1, - kDirectionalWheel = 2, - kRotatingWheel = 3, + enum class WheelInputModes : uint8_t { + kDisabled = 0, + kDirectionalWheel = 1, + kRotatingWheel = 2, }; - auto PrimaryInput() -> InputModes; - auto PrimaryInput(InputModes) -> void; + auto WheelInput() -> WheelInputModes; + auto WheelInput(WheelInputModes) -> void; - enum class LockedInputModes : uint8_t { + enum class ButtonInputModes : uint8_t { kDisabled = 0, kVolumeOnly = 1, + kMediaControls = 2, + kNavigation = 3, }; - auto LockedInput() -> LockedInputModes; - auto LockedInput(LockedInputModes) -> void; + auto ButtonInput() -> ButtonInputModes; + auto ButtonInput(ButtonInputModes) -> void; + + auto LockedInput() -> ButtonInputModes; + auto LockedInput(ButtonInputModes) -> void; auto QueueRepeatMode() -> uint8_t; auto QueueRepeatMode(uint8_t) -> void; @@ -191,7 +195,8 @@ class NvsStorage { Setting amp_max_vol_; Setting amp_cur_vol_; Setting amp_left_bias_; - Setting input_mode_; + Setting wheel_input_mode_; + Setting button_input_mode_; Setting locked_input_mode_; Setting output_mode_; Setting haptics_mode_; diff --git a/src/drivers/nvs.cpp b/src/drivers/nvs.cpp index 374c71d6..64ed9c1a 100644 --- a/src/drivers/nvs.cpp +++ b/src/drivers/nvs.cpp @@ -34,7 +34,8 @@ static constexpr char kKeyInterfaceTheme[] = "ui_theme"; static constexpr char kKeyAmpMaxVolume[] = "hp_vol_max"; static constexpr char kKeyAmpCurrentVolume[] = "hp_vol"; static constexpr char kKeyAmpLeftBias[] = "hp_bias"; -static constexpr char kKeyPrimaryInput[] = "in_pri"; +static constexpr char kKeyWheelInput[] = "in_wheel"; +static constexpr char kKeyButtonInput[] = "in_btn"; static constexpr char kKeyLockedInput[] = "in_locked"; static constexpr char kKeyHaptics[] = "haptic_mode"; static constexpr char kKeyScrollSensitivity[] = "scroll"; @@ -277,7 +278,8 @@ NvsStorage::NvsStorage(nvs_handle_t handle) amp_max_vol_(kKeyAmpMaxVolume), amp_cur_vol_(kKeyAmpCurrentVolume), amp_left_bias_(kKeyAmpLeftBias), - input_mode_(kKeyPrimaryInput), + wheel_input_mode_(kKeyWheelInput), + button_input_mode_(kKeyButtonInput), locked_input_mode_(kKeyLockedInput), output_mode_(kKeyOutput), haptics_mode_(kKeyHaptics), @@ -309,7 +311,8 @@ auto NvsStorage::Read() -> void { amp_max_vol_.read(handle_); amp_cur_vol_.read(handle_); amp_left_bias_.read(handle_); - input_mode_.read(handle_); + wheel_input_mode_.read(handle_); + button_input_mode_.read(handle_); locked_input_mode_.read(handle_); output_mode_.read(handle_); haptics_mode_.read(handle_); @@ -336,7 +339,8 @@ auto NvsStorage::Write() -> bool { amp_max_vol_.write(handle_); amp_cur_vol_.write(handle_); amp_left_bias_.write(handle_); - input_mode_.write(handle_); + wheel_input_mode_.write(handle_); + button_input_mode_.write(handle_); locked_input_mode_.write(handle_); output_mode_.write(handle_); haptics_mode_.write(handle_); @@ -615,40 +619,63 @@ auto NvsStorage::AmpLeftBias(int_fast8_t val) -> void { amp_left_bias_.set(val); } -auto NvsStorage::PrimaryInput() -> InputModes { +auto NvsStorage::WheelInput() -> WheelInputModes { std::lock_guard lock{mutex_}; - switch (input_mode_.get().value_or(3)) { - case static_cast(InputModes::kButtonsOnly): - return InputModes::kButtonsOnly; - case static_cast(InputModes::kButtonsWithWheel): - return InputModes::kButtonsWithWheel; - case static_cast(InputModes::kDirectionalWheel): - return InputModes::kDirectionalWheel; - case static_cast(InputModes::kRotatingWheel): - return InputModes::kRotatingWheel; + switch (wheel_input_mode_.get().value_or(3)) { + case static_cast(WheelInputModes::kDisabled): + return WheelInputModes::kDisabled; + case static_cast(WheelInputModes::kDirectionalWheel): + return WheelInputModes::kDirectionalWheel; + case static_cast(WheelInputModes::kRotatingWheel): + return WheelInputModes::kRotatingWheel; default: - return InputModes::kRotatingWheel; + return WheelInputModes::kRotatingWheel; } } -auto NvsStorage::PrimaryInput(InputModes mode) -> void { +auto NvsStorage::WheelInput(WheelInputModes mode) -> void { std::lock_guard lock{mutex_}; - input_mode_.set(static_cast(mode)); + wheel_input_mode_.set(static_cast(mode)); } -auto NvsStorage::LockedInput() -> LockedInputModes { +auto NvsStorage::ButtonInput() -> ButtonInputModes { std::lock_guard lock{mutex_}; - switch (locked_input_mode_.get().value_or(static_cast(LockedInputModes::kDisabled))) { - case static_cast(LockedInputModes::kDisabled): - return LockedInputModes::kDisabled; - case static_cast(LockedInputModes::kVolumeOnly): - return LockedInputModes::kVolumeOnly; + switch (button_input_mode_.get().value_or(static_cast(ButtonInputModes::kVolumeOnly))) { + case static_cast(ButtonInputModes::kDisabled): + return ButtonInputModes::kDisabled; + case static_cast(ButtonInputModes::kVolumeOnly): + return ButtonInputModes::kVolumeOnly; + case static_cast(ButtonInputModes::kMediaControls): + return ButtonInputModes::kMediaControls; + case static_cast(ButtonInputModes::kNavigation): + return ButtonInputModes::kNavigation; default: - return LockedInputModes::kDisabled; + return ButtonInputModes::kVolumeOnly; } } -auto NvsStorage::LockedInput(LockedInputModes mode) -> void { +auto NvsStorage::ButtonInput(ButtonInputModes mode) -> void { + std::lock_guard lock{mutex_}; + button_input_mode_.set(static_cast(mode)); +} + +auto NvsStorage::LockedInput() -> ButtonInputModes { + std::lock_guard lock{mutex_}; + switch (locked_input_mode_.get().value_or(static_cast(ButtonInputModes::kDisabled))) { + case static_cast(ButtonInputModes::kDisabled): + return ButtonInputModes::kDisabled; + case static_cast(ButtonInputModes::kVolumeOnly): + return ButtonInputModes::kVolumeOnly; + case static_cast(ButtonInputModes::kMediaControls): + return ButtonInputModes::kMediaControls; + case static_cast(ButtonInputModes::kNavigation): + return ButtonInputModes::kNavigation; + default: + return ButtonInputModes::kDisabled; + } +} + +auto NvsStorage::LockedInput(ButtonInputModes mode) -> void { std::lock_guard lock{mutex_}; locked_input_mode_.set(static_cast(mode)); } diff --git a/src/tangara/input/device_factory.cpp b/src/tangara/input/device_factory.cpp index fe2e2485..ff597556 100644 --- a/src/tangara/input/device_factory.cpp +++ b/src/tangara/input/device_factory.cpp @@ -16,6 +16,7 @@ #include "input/input_touch_dpad.hpp" #include "input/input_touch_wheel.hpp" #include "input/input_volume_buttons.hpp" +#include "input/input_media_buttons.hpp" namespace input { @@ -26,30 +27,73 @@ DeviceFactory::DeviceFactory( wheel_ = std::make_shared(services->nvs(), **services->touchwheel()); } + reset_ = std::make_shared(services_->gpios()); } -auto DeviceFactory::createInputs(drivers::NvsStorage::InputModes mode) +auto DeviceFactory::createLockedInputs() -> std::vector> { + auto locked_mode = services_->nvs().LockedInput(); std::vector> ret; - switch (mode) { - case drivers::NvsStorage::InputModes::kButtonsOnly: - ret.push_back(std::make_shared(services_->gpios())); + switch (locked_mode) { + case drivers::NvsStorage::ButtonInputModes::kDisabled: + break; + case drivers::NvsStorage::ButtonInputModes::kMediaControls: + ret.push_back(std::make_shared(services_->gpios(), services_->track_queue())); + break; + case drivers::NvsStorage::ButtonInputModes::kNavigation: + // I don't think we want navigation to work when locked? + // ret.push_back(std::make_shared(services_->gpios())); break; - case drivers::NvsStorage::InputModes::kDirectionalWheel: + case drivers::NvsStorage::ButtonInputModes::kVolumeOnly: ret.push_back(std::make_shared(services_->gpios())); + break; + } + ret.push_back(reset_); + return ret; +} + +auto DeviceFactory::createInputs() + -> std::vector> { + std::vector> ret; + auto wheel_mode = services_->nvs().WheelInput(); + auto buttons_mode = services_->nvs().ButtonInput(); + // Make sure we always have a navigational input + if (wheel_mode == drivers::NvsStorage::WheelInputModes::kDisabled) { + if (buttons_mode != drivers::NvsStorage::ButtonInputModes::kNavigation) { + wheel_mode = drivers::NvsStorage::WheelInputModes::kRotatingWheel; + services_->nvs().WheelInput(wheel_mode); + } + } + switch (wheel_mode) { + case drivers::NvsStorage::WheelInputModes::kDisabled: + break; + case drivers::NvsStorage::WheelInputModes::kDirectionalWheel: if (services_->touchwheel()) { ret.push_back(std::make_shared(**services_->touchwheel())); } break; - case drivers::NvsStorage::InputModes::kRotatingWheel: + case drivers::NvsStorage::WheelInputModes::kRotatingWheel: default: // Don't break input over a bad enum value. - ret.push_back(std::make_shared(services_->gpios())); if (wheel_) { ret.push_back(wheel_); } break; } - ret.push_back(std::make_shared(services_->gpios())); + switch (buttons_mode) { + case drivers::NvsStorage::ButtonInputModes::kDisabled: + break; + case drivers::NvsStorage::ButtonInputModes::kMediaControls: + ret.push_back(std::make_shared(services_->gpios(), services_->track_queue())); + break; + case drivers::NvsStorage::ButtonInputModes::kNavigation: + ret.push_back(std::make_shared(services_->gpios())); + break; + case drivers::NvsStorage::ButtonInputModes::kVolumeOnly: + default: + ret.push_back(std::make_shared(services_->gpios())); + break; + } + ret.push_back(reset_); return ret; } diff --git a/src/tangara/input/device_factory.hpp b/src/tangara/input/device_factory.hpp index 5044d025..1f95bbef 100644 --- a/src/tangara/input/device_factory.hpp +++ b/src/tangara/input/device_factory.hpp @@ -12,6 +12,7 @@ #include "input/feedback_device.hpp" #include "input/input_device.hpp" #include "input/input_touch_wheel.hpp" +#include "input/input_hard_reset.hpp" #include "drivers/nvs.hpp" #include "system_fsm/service_locator.hpp" @@ -21,7 +22,9 @@ class DeviceFactory { public: DeviceFactory(std::shared_ptr); - auto createInputs(drivers::NvsStorage::InputModes mode) + auto createInputs() + -> std::vector>; + auto createLockedInputs() -> std::vector>; auto createFeedbacks() -> std::vector>; @@ -34,6 +37,10 @@ class DeviceFactory { // HACK: the touchwheel is current a special case, since it's the only input // device that has some kind of setting/configuration; scroll sensitivity. std::shared_ptr wheel_; + + // Another special case, the hard reset input should persist between + // lock modes, and always be added to the created inputs + std::shared_ptr reset_; }; } // namespace input diff --git a/src/tangara/input/input_device.hpp b/src/tangara/input/input_device.hpp index 7b8d993d..183781bc 100644 --- a/src/tangara/input/input_device.hpp +++ b/src/tangara/input/input_device.hpp @@ -36,7 +36,7 @@ class IInputDevice { } /* Called by the LVGL driver when controls are being locked. */ - virtual auto onLock(drivers::NvsStorage::LockedInputModes) -> void {} + virtual auto onLock() -> void {} /* Called by the LVGL driver when controls are being unlocked. */ virtual auto onUnlock() -> void {} }; diff --git a/src/tangara/input/input_hook_actions.cpp b/src/tangara/input/input_hook_actions.cpp index fb6a677c..001d3fb0 100644 --- a/src/tangara/input/input_hook_actions.cpp +++ b/src/tangara/input/input_hook_actions.cpp @@ -62,11 +62,22 @@ auto volumeDown() -> HookCallback { }}; } -auto allActions() -> std::vector { - return { - select(), scrollUp(), scrollDown(), scrollToTop(), - scrollToBottom(), goBack(), volumeUp(), volumeDown(), - }; +auto nextTrack(audio::TrackQueue& queue) -> HookCallback { + return HookCallback{.name = "next_track", .fn = [&](lv_indev_data_t* d) { + queue.next(); + }}; +} + +auto prevTrack(audio::TrackQueue& queue) -> HookCallback { + return HookCallback{.name = "prev_track", .fn = [&](lv_indev_data_t* d) { + queue.previous(); + }}; +} + +auto togglePlayPause() -> HookCallback { + return HookCallback{.name = "toggle_play_pause", .fn = [&](lv_indev_data_t* d) { + events::Audio().Dispatch(audio::TogglePlayPause{}); + }}; } } // namespace actions diff --git a/src/tangara/input/input_hook_actions.hpp b/src/tangara/input/input_hook_actions.hpp index bca82af6..2db0b6e7 100644 --- a/src/tangara/input/input_hook_actions.hpp +++ b/src/tangara/input/input_hook_actions.hpp @@ -7,6 +7,7 @@ #pragma once #include "input/input_hook.hpp" +#include "audio/track_queue.hpp" namespace input { namespace actions { @@ -21,10 +22,13 @@ auto scrollToBottom() -> HookCallback; auto goBack() -> HookCallback; +auto togglePlayPause() -> HookCallback; + +auto nextTrack(audio::TrackQueue& queue) -> HookCallback; +auto prevTrack(audio::TrackQueue& queue) -> HookCallback; + auto volumeUp() -> HookCallback; auto volumeDown() -> HookCallback; -auto allActions() -> std::vector; - } // namespace actions } // namespace input diff --git a/src/tangara/input/input_media_buttons.cpp b/src/tangara/input/input_media_buttons.cpp new file mode 100644 index 00000000..94d7dd12 --- /dev/null +++ b/src/tangara/input/input_media_buttons.cpp @@ -0,0 +1,62 @@ +/* + * Copyright 2025 ailurux + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include "input/input_media_buttons.hpp" +#include "drivers/gpios.hpp" +#include "events/event_queue.hpp" +#include "input/input_hook_actions.hpp" + +namespace input { + +MediaButtons::MediaButtons(drivers::IGpios& gpios, audio::TrackQueue& queue) + : gpios_(gpios), + up_("upper", actions::nextTrack(queue), {}, {}, actions::volumeUp()), + down_("lower", actions::prevTrack(queue), {}, {}, actions::volumeDown()), + locked_(), + both_buttons_pressed_(false) {} + +auto MediaButtons::read(lv_indev_data_t* data, std::vector& events) -> void { + bool up = !gpios_.Get(drivers::IGpios::Pin::kKeyUp); + bool down = !gpios_.Get(drivers::IGpios::Pin::kKeyDown); + + if ((up && down)) { + up_.cancel(); + down_.cancel(); + both_buttons_pressed_ = true; + } else if (!up && !down) { + if (both_buttons_pressed_) { + std::invoke(actions::togglePlayPause().fn, data); + both_buttons_pressed_ = false; + return; + } + } + + if (both_buttons_pressed_) { + return; + } + + up_.update(up, data); + down_.update(down, data); +} + +auto MediaButtons::name() -> std::string { + return "buttons"; +} + +auto MediaButtons::triggers() + -> std::vector> { + return {up_, down_}; +} + +auto MediaButtons::onLock() -> void { + locked_ = true; +} + +auto MediaButtons::onUnlock() -> void { + locked_ = false; +} + +} // namespace input diff --git a/src/tangara/input/input_media_buttons.hpp b/src/tangara/input/input_media_buttons.hpp new file mode 100644 index 00000000..02ee90d3 --- /dev/null +++ b/src/tangara/input/input_media_buttons.hpp @@ -0,0 +1,43 @@ +/* + * Copyright 2025 ailurux + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#include + +#include "indev/lv_indev.h" + +#include "drivers/gpios.hpp" +#include "drivers/haptics.hpp" +#include "drivers/touchwheel.hpp" +#include "input/input_device.hpp" +#include "input/input_hook.hpp" + +namespace input { + +class MediaButtons : public IInputDevice { + public: + MediaButtons(drivers::IGpios&, audio::TrackQueue& queue); + + auto read(lv_indev_data_t* data, std::vector& events) -> void override; + + auto name() -> std::string override; + auto triggers() -> std::vector> override; + + auto onLock() -> void override; + auto onUnlock() -> void override; + + private: + drivers::IGpios& gpios_; + + TriggerHooks up_; + TriggerHooks down_; + + bool locked_; + bool both_buttons_pressed_; +}; + +} // namespace input diff --git a/src/tangara/input/input_nav_buttons.cpp b/src/tangara/input/input_nav_buttons.cpp index ede6b714..b1279b44 100644 --- a/src/tangara/input/input_nav_buttons.cpp +++ b/src/tangara/input/input_nav_buttons.cpp @@ -42,7 +42,7 @@ auto NavButtons::triggers() return {up_, down_}; } -auto NavButtons::onLock(drivers::NvsStorage::LockedInputModes mode) -> void { +auto NavButtons::onLock() -> void { locked_ = true; } diff --git a/src/tangara/input/input_nav_buttons.hpp b/src/tangara/input/input_nav_buttons.hpp index c28cce91..03b337d3 100644 --- a/src/tangara/input/input_nav_buttons.hpp +++ b/src/tangara/input/input_nav_buttons.hpp @@ -28,7 +28,7 @@ class NavButtons : public IInputDevice { auto name() -> std::string override; auto triggers() -> std::vector> override; - auto onLock(drivers::NvsStorage::LockedInputModes) -> void override; + auto onLock() -> void override; auto onUnlock() -> void override; private: diff --git a/src/tangara/input/input_touch_dpad.cpp b/src/tangara/input/input_touch_dpad.cpp index c866f37c..b6c61237 100644 --- a/src/tangara/input/input_touch_dpad.cpp +++ b/src/tangara/input/input_touch_dpad.cpp @@ -65,7 +65,7 @@ auto TouchDPad::triggers() return {centre_, up_, right_, down_, left_}; } -auto TouchDPad::onLock(drivers::NvsStorage::LockedInputModes mode) -> void { +auto TouchDPad::onLock() -> void { wheel_.LowPowerMode(true); locked_ = true; } diff --git a/src/tangara/input/input_touch_dpad.hpp b/src/tangara/input/input_touch_dpad.hpp index 82de26bf..bf772d73 100644 --- a/src/tangara/input/input_touch_dpad.hpp +++ b/src/tangara/input/input_touch_dpad.hpp @@ -27,7 +27,7 @@ class TouchDPad : public IInputDevice { auto name() -> std::string override; auto triggers() -> std::vector> override; - auto onLock(drivers::NvsStorage::LockedInputModes) -> void override; + auto onLock() -> void override; auto onUnlock() -> void override; private: diff --git a/src/tangara/input/input_touch_wheel.cpp b/src/tangara/input/input_touch_wheel.cpp index 29153396..ae192e31 100644 --- a/src/tangara/input/input_touch_wheel.cpp +++ b/src/tangara/input/input_touch_wheel.cpp @@ -130,7 +130,7 @@ auto TouchWheel::triggers() return {centre_, up_, right_, down_, left_}; } -auto TouchWheel::onLock(drivers::NvsStorage::LockedInputModes mode) -> void { +auto TouchWheel::onLock() -> void { wheel_.LowPowerMode(true); locked_ = true; } diff --git a/src/tangara/input/input_touch_wheel.hpp b/src/tangara/input/input_touch_wheel.hpp index b2583e85..3f2ed49e 100644 --- a/src/tangara/input/input_touch_wheel.hpp +++ b/src/tangara/input/input_touch_wheel.hpp @@ -30,7 +30,7 @@ class TouchWheel : public IInputDevice { auto name() -> std::string override; auto triggers() -> std::vector> override; - auto onLock(drivers::NvsStorage::LockedInputModes) -> void override; + auto onLock() -> void override; auto onUnlock() -> void override; auto sensitivity() -> lua::Property&; diff --git a/src/tangara/input/input_volume_buttons.cpp b/src/tangara/input/input_volume_buttons.cpp index ffeea18f..d6c50e2f 100644 --- a/src/tangara/input/input_volume_buttons.cpp +++ b/src/tangara/input/input_volume_buttons.cpp @@ -21,9 +21,7 @@ auto VolumeButtons::read(lv_indev_data_t* data, std::vector& events) bool up = !gpios_.Get(drivers::IGpios::Pin::kKeyUp); bool down = !gpios_.Get(drivers::IGpios::Pin::kKeyDown); - bool input_disabled = locked_.has_value() && (locked_ != drivers::NvsStorage::LockedInputModes::kVolumeOnly); - - if ((up && down) || input_disabled) { + if ((up && down)) { up = false; down = false; } @@ -41,12 +39,12 @@ auto VolumeButtons::triggers() return {up_, down_}; } -auto VolumeButtons::onLock(drivers::NvsStorage::LockedInputModes mode) -> void { - locked_ = mode; +auto VolumeButtons::onLock() -> void { + locked_ = true; } auto VolumeButtons::onUnlock() -> void { - locked_ = {}; + locked_ = false; } } // namespace input diff --git a/src/tangara/input/input_volume_buttons.hpp b/src/tangara/input/input_volume_buttons.hpp index 17f60d7e..8559d496 100644 --- a/src/tangara/input/input_volume_buttons.hpp +++ b/src/tangara/input/input_volume_buttons.hpp @@ -27,7 +27,7 @@ class VolumeButtons : public IInputDevice { auto name() -> std::string override; auto triggers() -> std::vector> override; - auto onLock(drivers::NvsStorage::LockedInputModes) -> void override; + auto onLock() -> void override; auto onUnlock() -> void override; private: @@ -36,8 +36,7 @@ class VolumeButtons : public IInputDevice { TriggerHooks up_; TriggerHooks down_; - // When locked, this contains the active mode - std::optional locked_; + bool locked_; }; } // namespace input diff --git a/src/tangara/input/lvgl_input_driver.cpp b/src/tangara/input/lvgl_input_driver.cpp index 03d5cbb7..b4bab365 100644 --- a/src/tangara/input/lvgl_input_driver.cpp +++ b/src/tangara/input/lvgl_input_driver.cpp @@ -55,63 +55,94 @@ static void focus_cb(lv_group_t* group) { namespace { -auto intToMode(int raw) -> std::optional { +auto intToWheelMode(int raw) + -> std::optional { switch (raw) { case 0: - return drivers::NvsStorage::InputModes::kButtonsOnly; + return drivers::NvsStorage::WheelInputModes::kDisabled; case 1: - return drivers::NvsStorage::InputModes::kButtonsWithWheel; + return drivers::NvsStorage::WheelInputModes::kDirectionalWheel; case 2: - return drivers::NvsStorage::InputModes::kDirectionalWheel; - case 3: - return drivers::NvsStorage::InputModes::kRotatingWheel; + return drivers::NvsStorage::WheelInputModes::kRotatingWheel; default: return {}; } } -auto intToLockedMode(int raw) -> std::optional { +auto intToButtonMode(int raw) + -> std::optional { switch (raw) { case 0: - return drivers::NvsStorage::LockedInputModes::kDisabled; + return drivers::NvsStorage::ButtonInputModes::kDisabled; case 1: - return drivers::NvsStorage::LockedInputModes::kVolumeOnly; + return drivers::NvsStorage::ButtonInputModes::kVolumeOnly; + case 2: + return drivers::NvsStorage::ButtonInputModes::kMediaControls; + case 3: + return drivers::NvsStorage::ButtonInputModes::kNavigation; default: return {}; } } -} // namespace {} +} // namespace LvglInputDriver::LvglInputDriver(drivers::NvsStorage& nvs, DeviceFactory& factory) : nvs_(nvs), factory_(factory), - mode_(static_cast(nvs.PrimaryInput()), - [&](const lua::LuaValue& val) { - if (!std::holds_alternative(val)) { - return false; - } - auto mode = intToMode(std::get(val)); - if (!mode) { - return false; - } - nvs.PrimaryInput(*mode); - inputs_ = factory.createInputs(*mode); - return true; - }), + wheel_mode_( + static_cast(nvs.WheelInput()), + [&](const lua::LuaValue& val) { + if (!std::holds_alternative(val)) { + return false; + } + auto mode = intToWheelMode(std::get(val)); + if (!mode) { + return false; + } + // Only allow disabling wheel if side buttons are + // used for navigation + if (*mode == drivers::NvsStorage::WheelInputModes::kDisabled && + nvs.ButtonInput() != + drivers::NvsStorage::ButtonInputModes::kNavigation) { + return false; + } + nvs.WheelInput(*mode); + inputs_ = factory.createInputs(); + return true; + }), + button_mode_( + static_cast(nvs.ButtonInput()), + [&](const lua::LuaValue& val) { + if (!std::holds_alternative(val)) { + return false; + } + auto mode = intToButtonMode(std::get(val)); + if (!mode) { + return false; + } + // Ensure we don't remove the only navigation control + if (*mode != drivers::NvsStorage::ButtonInputModes::kNavigation && + nvs.WheelInput() == drivers::NvsStorage::WheelInputModes::kDisabled) { + return false; + } + nvs.ButtonInput(*mode); + inputs_ = factory.createInputs(); + return true; + }), locked_mode_(static_cast(nvs.LockedInput()), - [&](const lua::LuaValue& val) { - if (!std::holds_alternative(val)) { - return false; - } - auto mode = intToLockedMode(std::get(val)); - if (!mode) { - return false; - } - nvs.LockedInput(*mode); - return true; - }), + [&](const lua::LuaValue& val) { + if (!std::holds_alternative(val)) { + return false; + } + auto mode = intToButtonMode(std::get(val)); + if (!mode) { + return false; + } + nvs.LockedInput(*mode); + return true; + }), haptics_mode_(static_cast(nvs.HapticsMode()), [&](const lua::LuaValue& val) { if (!std::holds_alternative(val)) { @@ -121,7 +152,7 @@ LvglInputDriver::LvglInputDriver(drivers::NvsStorage& nvs, nvs.HapticsMode(mode); return true; }), - inputs_(factory.createInputs(nvs.PrimaryInput())), + inputs_(factory.createInputs()), feedbacks_(factory.createFeedbacks()), is_locked_(false) { device_ = lv_indev_create(); @@ -172,14 +203,11 @@ auto LvglInputDriver::feedback(uint8_t event) -> void { auto LvglInputDriver::lock(bool l) -> void { is_locked_ = l; - auto locked_input_mode = nvs_.LockedInput(); - - for (auto&& device : inputs_) { - if (l) { - device->onLock(locked_input_mode); - } else { - device->onUnlock(); - } + // Todo:: Call onLock/onUnlock on inputs_? + if (l) { + inputs_ = factory_.createLockedInputs(); + } else { + inputs_ = factory_.createInputs(); } } diff --git a/src/tangara/input/lvgl_input_driver.hpp b/src/tangara/input/lvgl_input_driver.hpp index 629a0a78..ecb3a36d 100644 --- a/src/tangara/input/lvgl_input_driver.hpp +++ b/src/tangara/input/lvgl_input_driver.hpp @@ -35,7 +35,8 @@ class LvglInputDriver { public: LvglInputDriver(drivers::NvsStorage& nvs, DeviceFactory&); - auto mode() -> lua::Property& { return mode_; } + auto wheelMode() -> lua::Property& { return wheel_mode_; } + auto buttonMode() -> lua::Property& { return button_mode_; } auto lockedMode() -> lua::Property& { return locked_mode_; } auto hapticsMode() -> lua::Property& { return haptics_mode_; } @@ -50,7 +51,8 @@ class LvglInputDriver { drivers::NvsStorage& nvs_; DeviceFactory& factory_; - lua::Property mode_; + lua::Property wheel_mode_; + lua::Property button_mode_; lua::Property locked_mode_; lua::Property haptics_mode_; lv_indev_t* device_; @@ -73,8 +75,6 @@ class LvglInputDriver { 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&); @@ -96,13 +96,15 @@ class LvglInputDriver { std::string trigger_; std::map hooks_; }; - - /* A hook override implemented as a lua callback */ struct LuaOverride { lua_State* L; int ref; }; + /* Userdata object for tracking the Lua mirror of a TriggerHooks object. */ + + /* A hook override implemented as a lua callback */ + std::map overrides_; auto setOverride(lua_State* L, const OverrideSelector&) -> void; diff --git a/src/tangara/lua/lua_controls.cpp b/src/tangara/lua/lua_controls.cpp index bc2588ac..69053f43 100644 --- a/src/tangara/lua/lua_controls.cpp +++ b/src/tangara/lua/lua_controls.cpp @@ -23,37 +23,64 @@ namespace lua { [[maybe_unused]] static constexpr char kTag[] = "lua_controls"; -static auto controls_schemes(lua_State* L) -> int { +static auto wheel_schemes(lua_State* L) -> int { lua_newtable(L); - lua_pushliteral(L, "Buttons Only"); + lua_pushliteral(L, "Disabled"); lua_rawseti(L, -2, - static_cast(drivers::NvsStorage::InputModes::kButtonsOnly)); + static_cast(drivers::NvsStorage::WheelInputModes::kDisabled)); lua_pushliteral(L, "D-Pad"); + lua_rawseti(L, -2, + static_cast(drivers::NvsStorage::WheelInputModes::kDirectionalWheel)); + + lua_pushliteral(L, "Touchwheel"); + lua_rawseti( + L, -2, static_cast(drivers::NvsStorage::WheelInputModes::kRotatingWheel)); + + return 1; +} + +static auto button_schemes(lua_State* L) -> int { + lua_newtable(L); + + lua_pushliteral(L, "Disabled"); lua_rawseti( L, -2, - static_cast(drivers::NvsStorage::InputModes::kDirectionalWheel)); + static_cast(drivers::NvsStorage::ButtonInputModes::kDisabled)); - lua_pushliteral(L, "Touchwheel"); + lua_pushliteral(L, "Volume Only"); + lua_rawseti( + L, -2, + static_cast(drivers::NvsStorage::ButtonInputModes::kVolumeOnly)); + lua_pushliteral(L, "Media Controls"); lua_rawseti( - L, -2, static_cast(drivers::NvsStorage::InputModes::kRotatingWheel)); + L, -2, + static_cast(drivers::NvsStorage::ButtonInputModes::kMediaControls)); + lua_pushliteral(L, "Navigation"); + lua_rawseti( + L, -2, + static_cast(drivers::NvsStorage::ButtonInputModes::kNavigation)); return 1; } -static auto locked_controls_schemes(lua_State* L) -> int { +static auto locked_button_schemes(lua_State* L) -> int { lua_newtable(L); lua_pushliteral(L, "Disabled"); lua_rawseti( L, -2, - static_cast(drivers::NvsStorage::LockedInputModes::kDisabled)); + static_cast(drivers::NvsStorage::ButtonInputModes::kDisabled)); lua_pushliteral(L, "Volume Only"); lua_rawseti( L, -2, - static_cast(drivers::NvsStorage::LockedInputModes::kVolumeOnly)); + static_cast(drivers::NvsStorage::ButtonInputModes::kVolumeOnly)); + lua_pushliteral(L, "Media Controls"); + lua_rawseti( + L, -2, + static_cast(drivers::NvsStorage::ButtonInputModes::kMediaControls)); return 1; } @@ -77,8 +104,9 @@ static auto haptics_modes(lua_State* L) -> int { return 1; } -static const struct luaL_Reg kControlsFuncs[] = {{"schemes", controls_schemes}, - {"locked_schemes", locked_controls_schemes}, +static const struct luaL_Reg kControlsFuncs[] = {{"wheel_schemes", wheel_schemes}, + {"button_schemes", button_schemes}, + {"locked_schemes", locked_button_schemes}, {"haptics_modes", haptics_modes}, {NULL, NULL}}; diff --git a/src/tangara/ui/ui_fsm.cpp b/src/tangara/ui/ui_fsm.cpp index 020917e2..aaf35d13 100644 --- a/src/tangara/ui/ui_fsm.cpp +++ b/src/tangara/ui/ui_fsm.cpp @@ -680,7 +680,8 @@ void Lua::entry() { registry.AddPropertyModule( "controls", { - {"scheme", &sInput->mode()}, + {"wheel_scheme", &sInput->wheelMode()}, + {"button_scheme", &sInput->buttonMode()}, {"locked_scheme", &sInput->lockedMode()}, {"haptics_mode", &sInput->hapticsMode()}, {"lock_switch", &sLockSwitch},