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 <ailurux@noreply.codeberg.org>
Co-committed-by: ailurux <ailurux@noreply.codeberg.org>
custom
ailurux 1 month ago committed by cooljqln
parent 95dd0ddec5
commit c9ce88a457
  1. 2
      lua/main.lua
  2. 29
      lua/settings.lua
  3. 26
      lua/widgets.lua
  4. 4
      luals-stubs/controls.lua
  5. 27
      src/drivers/include/drivers/nvs.hpp
  6. 77
      src/drivers/nvs.cpp
  7. 60
      src/tangara/input/device_factory.cpp
  8. 9
      src/tangara/input/device_factory.hpp
  9. 2
      src/tangara/input/input_device.hpp
  10. 21
      src/tangara/input/input_hook_actions.cpp
  11. 8
      src/tangara/input/input_hook_actions.hpp
  12. 62
      src/tangara/input/input_media_buttons.cpp
  13. 43
      src/tangara/input/input_media_buttons.hpp
  14. 2
      src/tangara/input/input_nav_buttons.cpp
  15. 2
      src/tangara/input/input_nav_buttons.hpp
  16. 2
      src/tangara/input/input_touch_dpad.cpp
  17. 2
      src/tangara/input/input_touch_dpad.hpp
  18. 2
      src/tangara/input/input_touch_wheel.cpp
  19. 2
      src/tangara/input/input_touch_wheel.hpp
  20. 10
      src/tangara/input/input_volume_buttons.cpp
  21. 5
      src/tangara/input/input_volume_buttons.hpp
  22. 114
      src/tangara/input/lvgl_input_driver.cpp
  23. 14
      src/tangara/input/lvgl_input_driver.hpp
  24. 50
      src/tangara/lua/lua_controls.cpp
  25. 3
      src/tangara/ui/ui_fsm.cpp

@ -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.

@ -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
}

@ -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

@ -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.

@ -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<uint16_t> amp_max_vol_;
Setting<uint16_t> amp_cur_vol_;
Setting<int8_t> amp_left_bias_;
Setting<uint8_t> input_mode_;
Setting<uint8_t> wheel_input_mode_;
Setting<uint8_t> button_input_mode_;
Setting<uint8_t> locked_input_mode_;
Setting<uint8_t> output_mode_;
Setting<uint8_t> haptics_mode_;

@ -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<std::mutex> lock{mutex_};
switch (input_mode_.get().value_or(3)) {
case static_cast<uint8_t>(InputModes::kButtonsOnly):
return InputModes::kButtonsOnly;
case static_cast<uint8_t>(InputModes::kButtonsWithWheel):
return InputModes::kButtonsWithWheel;
case static_cast<uint8_t>(InputModes::kDirectionalWheel):
return InputModes::kDirectionalWheel;
case static_cast<uint8_t>(InputModes::kRotatingWheel):
return InputModes::kRotatingWheel;
switch (wheel_input_mode_.get().value_or(3)) {
case static_cast<uint8_t>(WheelInputModes::kDisabled):
return WheelInputModes::kDisabled;
case static_cast<uint8_t>(WheelInputModes::kDirectionalWheel):
return WheelInputModes::kDirectionalWheel;
case static_cast<uint8_t>(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<std::mutex> lock{mutex_};
input_mode_.set(static_cast<uint8_t>(mode));
wheel_input_mode_.set(static_cast<uint8_t>(mode));
}
auto NvsStorage::LockedInput() -> LockedInputModes {
auto NvsStorage::ButtonInput() -> ButtonInputModes {
std::lock_guard<std::mutex> lock{mutex_};
switch (locked_input_mode_.get().value_or(static_cast<uint8_t>(LockedInputModes::kDisabled))) {
case static_cast<uint8_t>(LockedInputModes::kDisabled):
return LockedInputModes::kDisabled;
case static_cast<uint8_t>(LockedInputModes::kVolumeOnly):
return LockedInputModes::kVolumeOnly;
switch (button_input_mode_.get().value_or(static_cast<uint8_t>(ButtonInputModes::kVolumeOnly))) {
case static_cast<uint8_t>(ButtonInputModes::kDisabled):
return ButtonInputModes::kDisabled;
case static_cast<uint8_t>(ButtonInputModes::kVolumeOnly):
return ButtonInputModes::kVolumeOnly;
case static_cast<uint8_t>(ButtonInputModes::kMediaControls):
return ButtonInputModes::kMediaControls;
case static_cast<uint8_t>(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<std::mutex> lock{mutex_};
button_input_mode_.set(static_cast<uint8_t>(mode));
}
auto NvsStorage::LockedInput() -> ButtonInputModes {
std::lock_guard<std::mutex> lock{mutex_};
switch (locked_input_mode_.get().value_or(static_cast<uint8_t>(ButtonInputModes::kDisabled))) {
case static_cast<uint8_t>(ButtonInputModes::kDisabled):
return ButtonInputModes::kDisabled;
case static_cast<uint8_t>(ButtonInputModes::kVolumeOnly):
return ButtonInputModes::kVolumeOnly;
case static_cast<uint8_t>(ButtonInputModes::kMediaControls):
return ButtonInputModes::kMediaControls;
case static_cast<uint8_t>(ButtonInputModes::kNavigation):
return ButtonInputModes::kNavigation;
default:
return ButtonInputModes::kDisabled;
}
}
auto NvsStorage::LockedInput(ButtonInputModes mode) -> void {
std::lock_guard<std::mutex> lock{mutex_};
locked_input_mode_.set(static_cast<uint8_t>(mode));
}

@ -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<TouchWheel>(services->nvs(), **services->touchwheel());
}
reset_ = std::make_shared<HardReset>(services_->gpios());
}
auto DeviceFactory::createInputs(drivers::NvsStorage::InputModes mode)
auto DeviceFactory::createLockedInputs()
-> std::vector<std::shared_ptr<IInputDevice>> {
auto locked_mode = services_->nvs().LockedInput();
std::vector<std::shared_ptr<IInputDevice>> ret;
switch (mode) {
case drivers::NvsStorage::InputModes::kButtonsOnly:
ret.push_back(std::make_shared<NavButtons>(services_->gpios()));
switch (locked_mode) {
case drivers::NvsStorage::ButtonInputModes::kDisabled:
break;
case drivers::NvsStorage::ButtonInputModes::kMediaControls:
ret.push_back(std::make_shared<MediaButtons>(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<NavButtons>(services_->gpios()));
break;
case drivers::NvsStorage::InputModes::kDirectionalWheel:
case drivers::NvsStorage::ButtonInputModes::kVolumeOnly:
ret.push_back(std::make_shared<VolumeButtons>(services_->gpios()));
break;
}
ret.push_back(reset_);
return ret;
}
auto DeviceFactory::createInputs()
-> std::vector<std::shared_ptr<IInputDevice>> {
std::vector<std::shared_ptr<IInputDevice>> 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<TouchDPad>(**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<VolumeButtons>(services_->gpios()));
if (wheel_) {
ret.push_back(wheel_);
}
break;
}
ret.push_back(std::make_shared<HardReset>(services_->gpios()));
switch (buttons_mode) {
case drivers::NvsStorage::ButtonInputModes::kDisabled:
break;
case drivers::NvsStorage::ButtonInputModes::kMediaControls:
ret.push_back(std::make_shared<MediaButtons>(services_->gpios(), services_->track_queue()));
break;
case drivers::NvsStorage::ButtonInputModes::kNavigation:
ret.push_back(std::make_shared<NavButtons>(services_->gpios()));
break;
case drivers::NvsStorage::ButtonInputModes::kVolumeOnly:
default:
ret.push_back(std::make_shared<VolumeButtons>(services_->gpios()));
break;
}
ret.push_back(reset_);
return ret;
}

@ -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<system_fsm::ServiceLocator>);
auto createInputs(drivers::NvsStorage::InputModes mode)
auto createInputs()
-> std::vector<std::shared_ptr<IInputDevice>>;
auto createLockedInputs()
-> std::vector<std::shared_ptr<IInputDevice>>;
auto createFeedbacks() -> std::vector<std::shared_ptr<IFeedbackDevice>>;
@ -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<TouchWheel> wheel_;
// Another special case, the hard reset input should persist between
// lock modes, and always be added to the created inputs
std::shared_ptr<HardReset> reset_;
};
} // namespace input

@ -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 {}
};

@ -62,11 +62,22 @@ auto volumeDown() -> HookCallback {
}};
}
auto allActions() -> std::vector<HookCallback> {
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

@ -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<HookCallback>;
} // namespace actions
} // namespace input

@ -0,0 +1,62 @@
/*
* Copyright 2025 ailurux <ailuruxx@gmail.com>
*
* 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<InputEvent>& 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<std::reference_wrapper<TriggerHooks>> {
return {up_, down_};
}
auto MediaButtons::onLock() -> void {
locked_ = true;
}
auto MediaButtons::onUnlock() -> void {
locked_ = false;
}
} // namespace input

@ -0,0 +1,43 @@
/*
* Copyright 2025 ailurux <ailuruxx@gmail.com>
*
* SPDX-License-Identifier: GPL-3.0-only
*/
#pragma once
#include <cstdint>
#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<InputEvent>& events) -> void override;
auto name() -> std::string override;
auto triggers() -> std::vector<std::reference_wrapper<TriggerHooks>> 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

@ -42,7 +42,7 @@ auto NavButtons::triggers()
return {up_, down_};
}
auto NavButtons::onLock(drivers::NvsStorage::LockedInputModes mode) -> void {
auto NavButtons::onLock() -> void {
locked_ = true;
}

@ -28,7 +28,7 @@ class NavButtons : public IInputDevice {
auto name() -> std::string override;
auto triggers() -> std::vector<std::reference_wrapper<TriggerHooks>> override;
auto onLock(drivers::NvsStorage::LockedInputModes) -> void override;
auto onLock() -> void override;
auto onUnlock() -> void override;
private:

@ -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;
}

@ -27,7 +27,7 @@ class TouchDPad : public IInputDevice {
auto name() -> std::string override;
auto triggers() -> std::vector<std::reference_wrapper<TriggerHooks>> override;
auto onLock(drivers::NvsStorage::LockedInputModes) -> void override;
auto onLock() -> void override;
auto onUnlock() -> void override;
private:

@ -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;
}

@ -30,7 +30,7 @@ class TouchWheel : public IInputDevice {
auto name() -> std::string override;
auto triggers() -> std::vector<std::reference_wrapper<TriggerHooks>> override;
auto onLock(drivers::NvsStorage::LockedInputModes) -> void override;
auto onLock() -> void override;
auto onUnlock() -> void override;
auto sensitivity() -> lua::Property&;

@ -21,9 +21,7 @@ auto VolumeButtons::read(lv_indev_data_t* data, std::vector<InputEvent>& 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

@ -27,7 +27,7 @@ class VolumeButtons : public IInputDevice {
auto name() -> std::string override;
auto triggers() -> std::vector<std::reference_wrapper<TriggerHooks>> 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<drivers::NvsStorage::LockedInputModes> locked_;
bool locked_;
};
} // namespace input

@ -55,63 +55,94 @@ static void focus_cb(lv_group_t* group) {
namespace {
auto intToMode(int raw) -> std::optional<drivers::NvsStorage::InputModes> {
auto intToWheelMode(int raw)
-> std::optional<drivers::NvsStorage::WheelInputModes> {
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<drivers::NvsStorage::LockedInputModes> {
auto intToButtonMode(int raw)
-> std::optional<drivers::NvsStorage::ButtonInputModes> {
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<int>(nvs.PrimaryInput()),
[&](const lua::LuaValue& val) {
if (!std::holds_alternative<int>(val)) {
return false;
}
auto mode = intToMode(std::get<int>(val));
if (!mode) {
return false;
}
nvs.PrimaryInput(*mode);
inputs_ = factory.createInputs(*mode);
return true;
}),
wheel_mode_(
static_cast<int>(nvs.WheelInput()),
[&](const lua::LuaValue& val) {
if (!std::holds_alternative<int>(val)) {
return false;
}
auto mode = intToWheelMode(std::get<int>(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<int>(nvs.ButtonInput()),
[&](const lua::LuaValue& val) {
if (!std::holds_alternative<int>(val)) {
return false;
}
auto mode = intToButtonMode(std::get<int>(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<int>(nvs.LockedInput()),
[&](const lua::LuaValue& val) {
if (!std::holds_alternative<int>(val)) {
return false;
}
auto mode = intToLockedMode(std::get<int>(val));
if (!mode) {
return false;
}
nvs.LockedInput(*mode);
return true;
}),
[&](const lua::LuaValue& val) {
if (!std::holds_alternative<int>(val)) {
return false;
}
auto mode = intToButtonMode(std::get<int>(val));
if (!mode) {
return false;
}
nvs.LockedInput(*mode);
return true;
}),
haptics_mode_(static_cast<int>(nvs.HapticsMode()),
[&](const lua::LuaValue& val) {
if (!std::holds_alternative<int>(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();
}
}

@ -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<std::string, std::string> 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<OverrideSelector, LuaOverride> overrides_;
auto setOverride(lua_State* L, const OverrideSelector&) -> void;

@ -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<int>(drivers::NvsStorage::InputModes::kButtonsOnly));
static_cast<int>(drivers::NvsStorage::WheelInputModes::kDisabled));
lua_pushliteral(L, "D-Pad");
lua_rawseti(L, -2,
static_cast<int>(drivers::NvsStorage::WheelInputModes::kDirectionalWheel));
lua_pushliteral(L, "Touchwheel");
lua_rawseti(
L, -2, static_cast<int>(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<int>(drivers::NvsStorage::InputModes::kDirectionalWheel));
static_cast<int>(drivers::NvsStorage::ButtonInputModes::kDisabled));
lua_pushliteral(L, "Touchwheel");
lua_pushliteral(L, "Volume Only");
lua_rawseti(
L, -2,
static_cast<int>(drivers::NvsStorage::ButtonInputModes::kVolumeOnly));
lua_pushliteral(L, "Media Controls");
lua_rawseti(
L, -2, static_cast<int>(drivers::NvsStorage::InputModes::kRotatingWheel));
L, -2,
static_cast<int>(drivers::NvsStorage::ButtonInputModes::kMediaControls));
lua_pushliteral(L, "Navigation");
lua_rawseti(
L, -2,
static_cast<int>(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<int>(drivers::NvsStorage::LockedInputModes::kDisabled));
static_cast<int>(drivers::NvsStorage::ButtonInputModes::kDisabled));
lua_pushliteral(L, "Volume Only");
lua_rawseti(
L, -2,
static_cast<int>(drivers::NvsStorage::LockedInputModes::kVolumeOnly));
static_cast<int>(drivers::NvsStorage::ButtonInputModes::kVolumeOnly));
lua_pushliteral(L, "Media Controls");
lua_rawseti(
L, -2,
static_cast<int>(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}};

@ -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},

Loading…
Cancel
Save