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. 72
      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 end
end), end),
controls.scheme:bind(function() controls.wheel_scheme:bind(function()
-- Set up a shortcut for jumping straight to the 'now playing' screen. -- 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 -- Implemented as a binding so that the shortcut is still applied even if
-- the control scheme is changed at runtime. -- the control scheme is changed at runtime.

@ -471,20 +471,33 @@ settings.InputSettings = SettingsScreen:new {
controls_chooser:onevent(lvgl.EVENT.VALUE_CHANGED, function() controls_chooser:onevent(lvgl.EVENT.VALUE_CHANGED, function()
local option = controls_chooser:get('selected') local option = controls_chooser:get('selected')
local scheme = option_to_scheme[option] 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) end)
return controls_chooser return controls_chooser
end end
theme.set_subject(self.content:Label { theme.set_subject(self.content:Label {
text = "Control scheme", text = "Wheel Controls",
}, "settings_title") }, "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") local controls_chooser_desc = widgets.Description(controls_chooser, "Control scheme")
theme.set_subject(self.content:Label { 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") }, "settings_title")
local controls_locked = make_scheme_control(self, controls.locked_schemes(), controls.locked_scheme) 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") 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) controls.scroll_sensitivity:set(sensitivity:value() * slider_scale)
end) end)
local sensitivity_desc = widgets.Description(sensitivity, "Scroll Sensitivity") 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 end
} }

@ -372,4 +372,30 @@ function widgets.InfiniteList(parent, iterator, opts)
return infinite_list return infinite_list
end 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 return widgets

@ -8,7 +8,9 @@
--- controls. These controls include the touchwheel, the lock switch, and the --- controls. These controls include the touchwheel, the lock switch, and the
--- side buttons. --- side buttons.
--- @class controls --- @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 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 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. --- @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;
auto AmpLeftBias(int_fast8_t) -> void; auto AmpLeftBias(int_fast8_t) -> void;
enum class InputModes : uint8_t { enum class WheelInputModes : uint8_t {
kButtonsOnly = 0, kDisabled = 0,
kButtonsWithWheel = 1, kDirectionalWheel = 1,
kDirectionalWheel = 2, kRotatingWheel = 2,
kRotatingWheel = 3,
}; };
auto PrimaryInput() -> InputModes; auto WheelInput() -> WheelInputModes;
auto PrimaryInput(InputModes) -> void; auto WheelInput(WheelInputModes) -> void;
enum class LockedInputModes : uint8_t { enum class ButtonInputModes : uint8_t {
kDisabled = 0, kDisabled = 0,
kVolumeOnly = 1, kVolumeOnly = 1,
kMediaControls = 2,
kNavigation = 3,
}; };
auto LockedInput() -> LockedInputModes; auto ButtonInput() -> ButtonInputModes;
auto LockedInput(LockedInputModes) -> void; auto ButtonInput(ButtonInputModes) -> void;
auto LockedInput() -> ButtonInputModes;
auto LockedInput(ButtonInputModes) -> void;
auto QueueRepeatMode() -> uint8_t; auto QueueRepeatMode() -> uint8_t;
auto QueueRepeatMode(uint8_t) -> void; auto QueueRepeatMode(uint8_t) -> void;
@ -191,7 +195,8 @@ class NvsStorage {
Setting<uint16_t> amp_max_vol_; Setting<uint16_t> amp_max_vol_;
Setting<uint16_t> amp_cur_vol_; Setting<uint16_t> amp_cur_vol_;
Setting<int8_t> amp_left_bias_; 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> locked_input_mode_;
Setting<uint8_t> output_mode_; Setting<uint8_t> output_mode_;
Setting<uint8_t> haptics_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 kKeyAmpMaxVolume[] = "hp_vol_max";
static constexpr char kKeyAmpCurrentVolume[] = "hp_vol"; static constexpr char kKeyAmpCurrentVolume[] = "hp_vol";
static constexpr char kKeyAmpLeftBias[] = "hp_bias"; 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 kKeyLockedInput[] = "in_locked";
static constexpr char kKeyHaptics[] = "haptic_mode"; static constexpr char kKeyHaptics[] = "haptic_mode";
static constexpr char kKeyScrollSensitivity[] = "scroll"; static constexpr char kKeyScrollSensitivity[] = "scroll";
@ -277,7 +278,8 @@ NvsStorage::NvsStorage(nvs_handle_t handle)
amp_max_vol_(kKeyAmpMaxVolume), amp_max_vol_(kKeyAmpMaxVolume),
amp_cur_vol_(kKeyAmpCurrentVolume), amp_cur_vol_(kKeyAmpCurrentVolume),
amp_left_bias_(kKeyAmpLeftBias), amp_left_bias_(kKeyAmpLeftBias),
input_mode_(kKeyPrimaryInput), wheel_input_mode_(kKeyWheelInput),
button_input_mode_(kKeyButtonInput),
locked_input_mode_(kKeyLockedInput), locked_input_mode_(kKeyLockedInput),
output_mode_(kKeyOutput), output_mode_(kKeyOutput),
haptics_mode_(kKeyHaptics), haptics_mode_(kKeyHaptics),
@ -309,7 +311,8 @@ auto NvsStorage::Read() -> void {
amp_max_vol_.read(handle_); amp_max_vol_.read(handle_);
amp_cur_vol_.read(handle_); amp_cur_vol_.read(handle_);
amp_left_bias_.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_); locked_input_mode_.read(handle_);
output_mode_.read(handle_); output_mode_.read(handle_);
haptics_mode_.read(handle_); haptics_mode_.read(handle_);
@ -336,7 +339,8 @@ auto NvsStorage::Write() -> bool {
amp_max_vol_.write(handle_); amp_max_vol_.write(handle_);
amp_cur_vol_.write(handle_); amp_cur_vol_.write(handle_);
amp_left_bias_.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_); locked_input_mode_.write(handle_);
output_mode_.write(handle_); output_mode_.write(handle_);
haptics_mode_.write(handle_); haptics_mode_.write(handle_);
@ -615,40 +619,63 @@ auto NvsStorage::AmpLeftBias(int_fast8_t val) -> void {
amp_left_bias_.set(val); amp_left_bias_.set(val);
} }
auto NvsStorage::PrimaryInput() -> InputModes { auto NvsStorage::WheelInput() -> WheelInputModes {
std::lock_guard<std::mutex> lock{mutex_}; std::lock_guard<std::mutex> lock{mutex_};
switch (input_mode_.get().value_or(3)) { switch (wheel_input_mode_.get().value_or(3)) {
case static_cast<uint8_t>(InputModes::kButtonsOnly): case static_cast<uint8_t>(WheelInputModes::kDisabled):
return InputModes::kButtonsOnly; return WheelInputModes::kDisabled;
case static_cast<uint8_t>(InputModes::kButtonsWithWheel): case static_cast<uint8_t>(WheelInputModes::kDirectionalWheel):
return InputModes::kButtonsWithWheel; return WheelInputModes::kDirectionalWheel;
case static_cast<uint8_t>(InputModes::kDirectionalWheel): case static_cast<uint8_t>(WheelInputModes::kRotatingWheel):
return InputModes::kDirectionalWheel; return WheelInputModes::kRotatingWheel;
case static_cast<uint8_t>(InputModes::kRotatingWheel):
return InputModes::kRotatingWheel;
default: 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_}; 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_}; std::lock_guard<std::mutex> lock{mutex_};
switch (locked_input_mode_.get().value_or(static_cast<uint8_t>(LockedInputModes::kDisabled))) { switch (button_input_mode_.get().value_or(static_cast<uint8_t>(ButtonInputModes::kVolumeOnly))) {
case static_cast<uint8_t>(LockedInputModes::kDisabled): case static_cast<uint8_t>(ButtonInputModes::kDisabled):
return LockedInputModes::kDisabled; return ButtonInputModes::kDisabled;
case static_cast<uint8_t>(LockedInputModes::kVolumeOnly): case static_cast<uint8_t>(ButtonInputModes::kVolumeOnly):
return LockedInputModes::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: 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_}; std::lock_guard<std::mutex> lock{mutex_};
locked_input_mode_.set(static_cast<uint8_t>(mode)); locked_input_mode_.set(static_cast<uint8_t>(mode));
} }

@ -16,6 +16,7 @@
#include "input/input_touch_dpad.hpp" #include "input/input_touch_dpad.hpp"
#include "input/input_touch_wheel.hpp" #include "input/input_touch_wheel.hpp"
#include "input/input_volume_buttons.hpp" #include "input/input_volume_buttons.hpp"
#include "input/input_media_buttons.hpp"
namespace input { namespace input {
@ -26,30 +27,73 @@ DeviceFactory::DeviceFactory(
wheel_ = wheel_ =
std::make_shared<TouchWheel>(services->nvs(), **services->touchwheel()); 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>> { -> std::vector<std::shared_ptr<IInputDevice>> {
auto locked_mode = services_->nvs().LockedInput();
std::vector<std::shared_ptr<IInputDevice>> ret; std::vector<std::shared_ptr<IInputDevice>> ret;
switch (mode) { switch (locked_mode) {
case drivers::NvsStorage::InputModes::kButtonsOnly: case drivers::NvsStorage::ButtonInputModes::kDisabled:
ret.push_back(std::make_shared<NavButtons>(services_->gpios())); 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; break;
case drivers::NvsStorage::InputModes::kDirectionalWheel: case drivers::NvsStorage::ButtonInputModes::kVolumeOnly:
ret.push_back(std::make_shared<VolumeButtons>(services_->gpios())); 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()) { if (services_->touchwheel()) {
ret.push_back(std::make_shared<TouchDPad>(**services_->touchwheel())); ret.push_back(std::make_shared<TouchDPad>(**services_->touchwheel()));
} }
break; break;
case drivers::NvsStorage::InputModes::kRotatingWheel: case drivers::NvsStorage::WheelInputModes::kRotatingWheel:
default: // Don't break input over a bad enum value. default: // Don't break input over a bad enum value.
ret.push_back(std::make_shared<VolumeButtons>(services_->gpios()));
if (wheel_) { if (wheel_) {
ret.push_back(wheel_); ret.push_back(wheel_);
} }
break; 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; return ret;
} }

@ -12,6 +12,7 @@
#include "input/feedback_device.hpp" #include "input/feedback_device.hpp"
#include "input/input_device.hpp" #include "input/input_device.hpp"
#include "input/input_touch_wheel.hpp" #include "input/input_touch_wheel.hpp"
#include "input/input_hard_reset.hpp"
#include "drivers/nvs.hpp" #include "drivers/nvs.hpp"
#include "system_fsm/service_locator.hpp" #include "system_fsm/service_locator.hpp"
@ -21,7 +22,9 @@ class DeviceFactory {
public: public:
DeviceFactory(std::shared_ptr<system_fsm::ServiceLocator>); 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>>; -> std::vector<std::shared_ptr<IInputDevice>>;
auto createFeedbacks() -> std::vector<std::shared_ptr<IFeedbackDevice>>; 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 // HACK: the touchwheel is current a special case, since it's the only input
// device that has some kind of setting/configuration; scroll sensitivity. // device that has some kind of setting/configuration; scroll sensitivity.
std::shared_ptr<TouchWheel> wheel_; 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 } // namespace input

@ -36,7 +36,7 @@ class IInputDevice {
} }
/* Called by the LVGL driver when controls are being locked. */ /* 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. */ /* Called by the LVGL driver when controls are being unlocked. */
virtual auto onUnlock() -> void {} virtual auto onUnlock() -> void {}
}; };

@ -62,11 +62,22 @@ auto volumeDown() -> HookCallback {
}}; }};
} }
auto allActions() -> std::vector<HookCallback> { auto nextTrack(audio::TrackQueue& queue) -> HookCallback {
return { return HookCallback{.name = "next_track", .fn = [&](lv_indev_data_t* d) {
select(), scrollUp(), scrollDown(), scrollToTop(), queue.next();
scrollToBottom(), goBack(), volumeUp(), volumeDown(), }};
}; }
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 } // namespace actions

@ -7,6 +7,7 @@
#pragma once #pragma once
#include "input/input_hook.hpp" #include "input/input_hook.hpp"
#include "audio/track_queue.hpp"
namespace input { namespace input {
namespace actions { namespace actions {
@ -21,10 +22,13 @@ auto scrollToBottom() -> HookCallback;
auto goBack() -> HookCallback; auto goBack() -> HookCallback;
auto togglePlayPause() -> HookCallback;
auto nextTrack(audio::TrackQueue& queue) -> HookCallback;
auto prevTrack(audio::TrackQueue& queue) -> HookCallback;
auto volumeUp() -> HookCallback; auto volumeUp() -> HookCallback;
auto volumeDown() -> HookCallback; auto volumeDown() -> HookCallback;
auto allActions() -> std::vector<HookCallback>;
} // namespace actions } // namespace actions
} // namespace input } // 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_}; return {up_, down_};
} }
auto NavButtons::onLock(drivers::NvsStorage::LockedInputModes mode) -> void { auto NavButtons::onLock() -> void {
locked_ = true; locked_ = true;
} }

@ -28,7 +28,7 @@ class NavButtons : public IInputDevice {
auto name() -> std::string override; auto name() -> std::string override;
auto triggers() -> std::vector<std::reference_wrapper<TriggerHooks>> 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 onUnlock() -> void override;
private: private:

@ -65,7 +65,7 @@ auto TouchDPad::triggers()
return {centre_, up_, right_, down_, left_}; return {centre_, up_, right_, down_, left_};
} }
auto TouchDPad::onLock(drivers::NvsStorage::LockedInputModes mode) -> void { auto TouchDPad::onLock() -> void {
wheel_.LowPowerMode(true); wheel_.LowPowerMode(true);
locked_ = true; locked_ = true;
} }

@ -27,7 +27,7 @@ class TouchDPad : public IInputDevice {
auto name() -> std::string override; auto name() -> std::string override;
auto triggers() -> std::vector<std::reference_wrapper<TriggerHooks>> 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 onUnlock() -> void override;
private: private:

@ -130,7 +130,7 @@ auto TouchWheel::triggers()
return {centre_, up_, right_, down_, left_}; return {centre_, up_, right_, down_, left_};
} }
auto TouchWheel::onLock(drivers::NvsStorage::LockedInputModes mode) -> void { auto TouchWheel::onLock() -> void {
wheel_.LowPowerMode(true); wheel_.LowPowerMode(true);
locked_ = true; locked_ = true;
} }

@ -30,7 +30,7 @@ class TouchWheel : public IInputDevice {
auto name() -> std::string override; auto name() -> std::string override;
auto triggers() -> std::vector<std::reference_wrapper<TriggerHooks>> 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 onUnlock() -> void override;
auto sensitivity() -> lua::Property&; 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 up = !gpios_.Get(drivers::IGpios::Pin::kKeyUp);
bool down = !gpios_.Get(drivers::IGpios::Pin::kKeyDown); bool down = !gpios_.Get(drivers::IGpios::Pin::kKeyDown);
bool input_disabled = locked_.has_value() && (locked_ != drivers::NvsStorage::LockedInputModes::kVolumeOnly); if ((up && down)) {
if ((up && down) || input_disabled) {
up = false; up = false;
down = false; down = false;
} }
@ -41,12 +39,12 @@ auto VolumeButtons::triggers()
return {up_, down_}; return {up_, down_};
} }
auto VolumeButtons::onLock(drivers::NvsStorage::LockedInputModes mode) -> void { auto VolumeButtons::onLock() -> void {
locked_ = mode; locked_ = true;
} }
auto VolumeButtons::onUnlock() -> void { auto VolumeButtons::onUnlock() -> void {
locked_ = {}; locked_ = false;
} }
} // namespace input } // namespace input

@ -27,7 +27,7 @@ class VolumeButtons : public IInputDevice {
auto name() -> std::string override; auto name() -> std::string override;
auto triggers() -> std::vector<std::reference_wrapper<TriggerHooks>> 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 onUnlock() -> void override;
private: private:
@ -36,8 +36,7 @@ class VolumeButtons : public IInputDevice {
TriggerHooks up_; TriggerHooks up_;
TriggerHooks down_; TriggerHooks down_;
// When locked, this contains the active mode bool locked_;
std::optional<drivers::NvsStorage::LockedInputModes> locked_;
}; };
} // namespace input } // namespace input

@ -55,49 +55,80 @@ static void focus_cb(lv_group_t* group) {
namespace { namespace {
auto intToMode(int raw) -> std::optional<drivers::NvsStorage::InputModes> { auto intToWheelMode(int raw)
-> std::optional<drivers::NvsStorage::WheelInputModes> {
switch (raw) { switch (raw) {
case 0: case 0:
return drivers::NvsStorage::InputModes::kButtonsOnly; return drivers::NvsStorage::WheelInputModes::kDisabled;
case 1: case 1:
return drivers::NvsStorage::InputModes::kButtonsWithWheel; return drivers::NvsStorage::WheelInputModes::kDirectionalWheel;
case 2: case 2:
return drivers::NvsStorage::InputModes::kDirectionalWheel; return drivers::NvsStorage::WheelInputModes::kRotatingWheel;
case 3:
return drivers::NvsStorage::InputModes::kRotatingWheel;
default: default:
return {}; return {};
} }
} }
auto intToLockedMode(int raw) -> std::optional<drivers::NvsStorage::LockedInputModes> { auto intToButtonMode(int raw)
-> std::optional<drivers::NvsStorage::ButtonInputModes> {
switch (raw) { switch (raw) {
case 0: case 0:
return drivers::NvsStorage::LockedInputModes::kDisabled; return drivers::NvsStorage::ButtonInputModes::kDisabled;
case 1: 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: default:
return {}; return {};
} }
} }
} // namespace {} } // namespace
LvglInputDriver::LvglInputDriver(drivers::NvsStorage& nvs, LvglInputDriver::LvglInputDriver(drivers::NvsStorage& nvs,
DeviceFactory& factory) DeviceFactory& factory)
: nvs_(nvs), : nvs_(nvs),
factory_(factory), factory_(factory),
mode_(static_cast<int>(nvs.PrimaryInput()), wheel_mode_(
static_cast<int>(nvs.WheelInput()),
[&](const lua::LuaValue& val) { [&](const lua::LuaValue& val) {
if (!std::holds_alternative<int>(val)) { if (!std::holds_alternative<int>(val)) {
return false; return false;
} }
auto mode = intToMode(std::get<int>(val)); auto mode = intToWheelMode(std::get<int>(val));
if (!mode) { if (!mode) {
return false; return false;
} }
nvs.PrimaryInput(*mode); // Only allow disabling wheel if side buttons are
inputs_ = factory.createInputs(*mode); // 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; return true;
}), }),
locked_mode_(static_cast<int>(nvs.LockedInput()), locked_mode_(static_cast<int>(nvs.LockedInput()),
@ -105,7 +136,7 @@ LvglInputDriver::LvglInputDriver(drivers::NvsStorage& nvs,
if (!std::holds_alternative<int>(val)) { if (!std::holds_alternative<int>(val)) {
return false; return false;
} }
auto mode = intToLockedMode(std::get<int>(val)); auto mode = intToButtonMode(std::get<int>(val));
if (!mode) { if (!mode) {
return false; return false;
} }
@ -121,7 +152,7 @@ LvglInputDriver::LvglInputDriver(drivers::NvsStorage& nvs,
nvs.HapticsMode(mode); nvs.HapticsMode(mode);
return true; return true;
}), }),
inputs_(factory.createInputs(nvs.PrimaryInput())), inputs_(factory.createInputs()),
feedbacks_(factory.createFeedbacks()), feedbacks_(factory.createFeedbacks()),
is_locked_(false) { is_locked_(false) {
device_ = lv_indev_create(); device_ = lv_indev_create();
@ -172,14 +203,11 @@ auto LvglInputDriver::feedback(uint8_t event) -> void {
auto LvglInputDriver::lock(bool l) -> void { auto LvglInputDriver::lock(bool l) -> void {
is_locked_ = l; is_locked_ = l;
auto locked_input_mode = nvs_.LockedInput(); // Todo:: Call onLock/onUnlock on inputs_?
for (auto&& device : inputs_) {
if (l) { if (l) {
device->onLock(locked_input_mode); inputs_ = factory_.createLockedInputs();
} else { } else {
device->onUnlock(); inputs_ = factory_.createInputs();
}
} }
} }

@ -35,7 +35,8 @@ class LvglInputDriver {
public: public:
LvglInputDriver(drivers::NvsStorage& nvs, DeviceFactory&); 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 lockedMode() -> lua::Property& { return locked_mode_; }
auto hapticsMode() -> lua::Property& { return haptics_mode_; } auto hapticsMode() -> lua::Property& { return haptics_mode_; }
@ -50,7 +51,8 @@ class LvglInputDriver {
drivers::NvsStorage& nvs_; drivers::NvsStorage& nvs_;
DeviceFactory& factory_; DeviceFactory& factory_;
lua::Property mode_; lua::Property wheel_mode_;
lua::Property button_mode_;
lua::Property locked_mode_; lua::Property locked_mode_;
lua::Property haptics_mode_; lua::Property haptics_mode_;
lv_indev_t* device_; lv_indev_t* device_;
@ -73,8 +75,6 @@ class LvglInputDriver {
std::tie(r.device_name, r.trigger_name, r.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 { class LuaTrigger {
public: public:
LuaTrigger(LvglInputDriver&, IInputDevice&, TriggerHooks&); LuaTrigger(LvglInputDriver&, IInputDevice&, TriggerHooks&);
@ -96,13 +96,15 @@ class LvglInputDriver {
std::string trigger_; std::string trigger_;
std::map<std::string, std::string> hooks_; std::map<std::string, std::string> hooks_;
}; };
/* A hook override implemented as a lua callback */
struct LuaOverride { struct LuaOverride {
lua_State* L; lua_State* L;
int ref; 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_; std::map<OverrideSelector, LuaOverride> overrides_;
auto setOverride(lua_State* L, const OverrideSelector&) -> void; auto setOverride(lua_State* L, const OverrideSelector&) -> void;

@ -23,37 +23,64 @@ namespace lua {
[[maybe_unused]] static constexpr char kTag[] = "lua_controls"; [[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_newtable(L);
lua_pushliteral(L, "Buttons Only"); lua_pushliteral(L, "Disabled");
lua_rawseti(L, -2, lua_rawseti(L, -2,
static_cast<int>(drivers::NvsStorage::InputModes::kButtonsOnly)); static_cast<int>(drivers::NvsStorage::WheelInputModes::kDisabled));
lua_pushliteral(L, "D-Pad"); 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( lua_rawseti(
L, -2, 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( 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; return 1;
} }
static auto locked_controls_schemes(lua_State* L) -> int { static auto locked_button_schemes(lua_State* L) -> int {
lua_newtable(L); lua_newtable(L);
lua_pushliteral(L, "Disabled"); lua_pushliteral(L, "Disabled");
lua_rawseti( lua_rawseti(
L, -2, L, -2,
static_cast<int>(drivers::NvsStorage::LockedInputModes::kDisabled)); static_cast<int>(drivers::NvsStorage::ButtonInputModes::kDisabled));
lua_pushliteral(L, "Volume Only"); lua_pushliteral(L, "Volume Only");
lua_rawseti( lua_rawseti(
L, -2, 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; return 1;
} }
@ -77,8 +104,9 @@ static auto haptics_modes(lua_State* L) -> int {
return 1; return 1;
} }
static const struct luaL_Reg kControlsFuncs[] = {{"schemes", controls_schemes}, static const struct luaL_Reg kControlsFuncs[] = {{"wheel_schemes", wheel_schemes},
{"locked_schemes", locked_controls_schemes}, {"button_schemes", button_schemes},
{"locked_schemes", locked_button_schemes},
{"haptics_modes", haptics_modes}, {"haptics_modes", haptics_modes},
{NULL, NULL}}; {NULL, NULL}};

@ -680,7 +680,8 @@ void Lua::entry() {
registry.AddPropertyModule( registry.AddPropertyModule(
"controls", "controls",
{ {
{"scheme", &sInput->mode()}, {"wheel_scheme", &sInput->wheelMode()},
{"button_scheme", &sInput->buttonMode()},
{"locked_scheme", &sInput->lockedMode()}, {"locked_scheme", &sInput->lockedMode()},
{"haptics_mode", &sInput->hapticsMode()}, {"haptics_mode", &sInput->hapticsMode()},
{"lock_switch", &sLockSwitch}, {"lock_switch", &sLockSwitch},

Loading…
Cancel
Save