summaryrefslogtreecommitdiff
path: root/src/tangara/input
diff options
context:
space:
mode:
authorjacqueline <me@jacqueline.id.au>2024-05-29 14:45:49 +1000
committerjacqueline <me@jacqueline.id.au>2024-05-29 14:45:49 +1000
commit2ff8eac022f397bb1aed28aca376fbe422fc8b3c (patch)
treeae80d0d89a212b1badf1d971fc67e701a9e4e962 /src/tangara/input
parentef812a53e5a84665e74be8c46cb983edaa712b3f (diff)
downloadtangara-fw-2ff8eac022f397bb1aed28aca376fbe422fc8b3c.tar.gz
Start on TTS support by logging the data that will become TTS lines
Includes some misc cleanup of haptic double-triggering (or non-triggering), since those cases all end up being TTS event double-reporting, which to me crosses the threshold from "annoying" to "usability issue"
Diffstat (limited to 'src/tangara/input')
-rw-r--r--src/tangara/input/device_factory.cpp6
-rw-r--r--src/tangara/input/feedback_device.hpp3
-rw-r--r--src/tangara/input/feedback_haptics.cpp9
-rw-r--r--src/tangara/input/feedback_haptics.hpp5
-rw-r--r--src/tangara/input/feedback_tts.cpp97
-rw-r--r--src/tangara/input/feedback_tts.hpp36
-rw-r--r--src/tangara/input/lvgl_input_driver.cpp14
-rw-r--r--src/tangara/input/lvgl_input_driver.hpp6
8 files changed, 168 insertions, 8 deletions
diff --git a/src/tangara/input/device_factory.cpp b/src/tangara/input/device_factory.cpp
index 8e1c5155..09fd2fd2 100644
--- a/src/tangara/input/device_factory.cpp
+++ b/src/tangara/input/device_factory.cpp
@@ -9,6 +9,7 @@
#include <memory>
#include "input/feedback_haptics.hpp"
+#include "input/feedback_tts.hpp"
#include "input/input_device.hpp"
#include "input/input_nav_buttons.hpp"
#include "input/input_touch_dpad.hpp"
@@ -52,7 +53,10 @@ auto DeviceFactory::createInputs(drivers::NvsStorage::InputModes mode)
auto DeviceFactory::createFeedbacks()
-> std::vector<std::shared_ptr<IFeedbackDevice>> {
- return {std::make_shared<Haptics>(services_->haptics())};
+ return {
+ std::make_shared<Haptics>(services_->haptics()),
+ std::make_shared<TextToSpeech>(services_->tts()),
+ };
}
} // namespace input
diff --git a/src/tangara/input/feedback_device.hpp b/src/tangara/input/feedback_device.hpp
index 4faeeafd..8253642f 100644
--- a/src/tangara/input/feedback_device.hpp
+++ b/src/tangara/input/feedback_device.hpp
@@ -7,6 +7,7 @@
#pragma once
#include <cstdint>
+#include "core/lv_group.h"
namespace input {
@@ -23,7 +24,7 @@ class IFeedbackDevice {
public:
virtual ~IFeedbackDevice() {}
- virtual auto feedback(uint8_t event_type) -> void = 0;
+ virtual auto feedback(lv_group_t* group, uint8_t event_type) -> void = 0;
// TODO: Add configuration; likely the same shape of interface that
// IInputDevice uses.
diff --git a/src/tangara/input/feedback_haptics.cpp b/src/tangara/input/feedback_haptics.cpp
index e690eb9f..c834ca54 100644
--- a/src/tangara/input/feedback_haptics.cpp
+++ b/src/tangara/input/feedback_haptics.cpp
@@ -8,6 +8,7 @@
#include <cstdint>
+#include "core/lv_group.h"
#include "lvgl/lvgl.h"
#include "core/lv_event.h"
@@ -21,7 +22,13 @@ using Effect = drivers::Haptics::Effect;
Haptics::Haptics(drivers::Haptics& haptics_) : haptics_(haptics_) {}
-auto Haptics::feedback(uint8_t event_type) -> void {
+auto Haptics::feedback(lv_group_t* group, uint8_t event_type) -> void {
+ lv_obj_t* obj = lv_group_get_focused(group);
+ if (obj == last_selection_) {
+ return;
+ }
+ last_selection_ = obj;
+
switch (event_type) {
case LV_EVENT_FOCUSED:
haptics_.PlayWaveformEffect(Effect::kMediumClick1_100Pct);
diff --git a/src/tangara/input/feedback_haptics.hpp b/src/tangara/input/feedback_haptics.hpp
index bde5f345..91d7ec3a 100644
--- a/src/tangara/input/feedback_haptics.hpp
+++ b/src/tangara/input/feedback_haptics.hpp
@@ -8,6 +8,8 @@
#include <cstdint>
+#include "core/lv_obj.h"
+
#include "drivers/haptics.hpp"
#include "input/feedback_device.hpp"
@@ -17,10 +19,11 @@ class Haptics : public IFeedbackDevice {
public:
Haptics(drivers::Haptics& haptics_);
- auto feedback(uint8_t event_type) -> void override;
+ auto feedback(lv_group_t*, uint8_t event_type) -> void override;
private:
drivers::Haptics& haptics_;
+ lv_obj_t* last_selection_;
};
} // namespace input
diff --git a/src/tangara/input/feedback_tts.cpp b/src/tangara/input/feedback_tts.cpp
new file mode 100644
index 00000000..a9267aa8
--- /dev/null
+++ b/src/tangara/input/feedback_tts.cpp
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2024 jacqueline <me@jacqueline.id.au>
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include "input/feedback_tts.hpp"
+
+#include <cstdint>
+#include <variant>
+
+#include "lvgl/lvgl.h"
+
+#include "core/lv_event.h"
+#include "core/lv_group.h"
+#include "core/lv_obj.h"
+#include "core/lv_obj_class.h"
+#include "core/lv_obj_tree.h"
+#include "extra/widgets/list/lv_list.h"
+#include "tts/events.hpp"
+#include "widgets/lv_label.h"
+
+#include "tts/events.hpp"
+#include "tts/provider.hpp"
+
+namespace input {
+
+TextToSpeech::TextToSpeech(tts::Provider& tts)
+ : tts_(tts), last_obj_(nullptr) {}
+
+auto TextToSpeech::feedback(lv_group_t* group, uint8_t event_type) -> void {
+ if (group != last_group_) {
+ last_group_ = group;
+ last_obj_ = nullptr;
+ if (group) {
+ tts_.feed(tts::SimpleEvent::kContextChanged);
+ }
+ }
+
+ if (group) {
+ lv_obj_t* focused = lv_group_get_focused(group);
+ if (focused == last_obj_) {
+ return;
+ }
+
+ last_obj_ = focused;
+ if (focused != nullptr) {
+ describe(*focused);
+ }
+ }
+}
+
+auto TextToSpeech::describe(lv_obj_t& obj) -> void {
+ if (lv_obj_check_type(&obj, &lv_btn_class) ||
+ lv_obj_check_type(&obj, &lv_list_btn_class)) {
+ auto desc = findDescription(obj);
+ tts_.feed(tts::SelectionChanged{
+ .new_selection =
+ tts::SelectionChanged::Selection{
+ .description = desc,
+ .is_interactive = true,
+ },
+ });
+ } else {
+ auto desc = findDescription(obj);
+ tts_.feed(tts::SelectionChanged{
+ .new_selection =
+ tts::SelectionChanged::Selection{
+ .description = desc,
+ .is_interactive = false,
+ },
+ });
+ }
+}
+
+auto TextToSpeech::findDescription(lv_obj_t& obj)
+ -> std::optional<std::string> {
+ if (lv_obj_get_child_cnt(&obj) > 0) {
+ for (size_t i = 0; i < lv_obj_get_child_cnt(&obj); i++) {
+ auto res = findDescription(*lv_obj_get_child(&obj, i));
+ if (res) {
+ return res;
+ }
+ }
+ }
+
+ if (lv_obj_check_type(&obj, &lv_label_class)) {
+ std::string text = lv_label_get_text(&obj);
+ if (!text.empty()) {
+ return text;
+ }
+ }
+
+ return {};
+}
+
+} // namespace input
diff --git a/src/tangara/input/feedback_tts.hpp b/src/tangara/input/feedback_tts.hpp
new file mode 100644
index 00000000..ddd83ff0
--- /dev/null
+++ b/src/tangara/input/feedback_tts.hpp
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2024 jacqueline <me@jacqueline.id.au>
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#pragma once
+
+#include <cstdint>
+
+#include "core/lv_obj.h"
+#include "drivers/haptics.hpp"
+
+#include "input/feedback_device.hpp"
+#include "tts/events.hpp"
+#include "tts/provider.hpp"
+
+namespace input {
+
+class TextToSpeech : public IFeedbackDevice {
+ public:
+ TextToSpeech(tts::Provider&);
+
+ auto feedback(lv_group_t*, uint8_t event_type) -> void override;
+
+ private:
+ tts::Provider& tts_;
+
+ auto describe(lv_obj_t&) -> void;
+ auto findDescription(lv_obj_t&) -> std::optional<std::string>;
+
+ lv_group_t* last_group_;
+ lv_obj_t* last_obj_;
+};
+
+} // namespace input
diff --git a/src/tangara/input/lvgl_input_driver.cpp b/src/tangara/input/lvgl_input_driver.cpp
index 8d10bb13..9c1ccff9 100644
--- a/src/tangara/input/lvgl_input_driver.cpp
+++ b/src/tangara/input/lvgl_input_driver.cpp
@@ -10,6 +10,8 @@
#include <memory>
#include <variant>
+#include "core/lv_event.h"
+#include "core/lv_indev.h"
#include "lua.hpp"
#include "lvgl.h"
@@ -91,6 +93,16 @@ LvglInputDriver::LvglInputDriver(drivers::NvsStorage& nvs,
registration_ = lv_indev_drv_register(&driver_);
}
+auto LvglInputDriver::setGroup(lv_group_t* g) -> void {
+ if (!g) {
+ return;
+ }
+ lv_indev_set_group(registration_, g);
+ // Emit a synthetic 'focus' event for the current selection, since otherwise
+ // our feedback devices won't know that the selection changed.
+ feedback(LV_EVENT_FOCUSED);
+}
+
auto LvglInputDriver::read(lv_indev_data_t* data) -> void {
// TODO: we should pass lock state on to the individual devices, since they
// may wish to either ignore the lock state, or power down until unlock.
@@ -107,7 +119,7 @@ auto LvglInputDriver::feedback(uint8_t event) -> void {
return;
}
for (auto&& device : feedbacks_) {
- device->feedback(event);
+ device->feedback(registration_->group, event);
}
}
diff --git a/src/tangara/input/lvgl_input_driver.hpp b/src/tangara/input/lvgl_input_driver.hpp
index 8ede1855..0b6a7e76 100644
--- a/src/tangara/input/lvgl_input_driver.hpp
+++ b/src/tangara/input/lvgl_input_driver.hpp
@@ -17,12 +17,12 @@
#include "input/device_factory.hpp"
#include "input/feedback_device.hpp"
+#include "drivers/nvs.hpp"
+#include "drivers/touchwheel.hpp"
#include "input/input_device.hpp"
#include "input/input_hook.hpp"
#include "lua/lua_thread.hpp"
#include "lua/property.hpp"
-#include "drivers/nvs.hpp"
-#include "drivers/touchwheel.hpp"
namespace input {
@@ -37,10 +37,10 @@ class LvglInputDriver {
auto mode() -> lua::Property& { return mode_; }
+ auto setGroup(lv_group_t*) -> void;
auto read(lv_indev_data_t* data) -> void;
auto feedback(uint8_t) -> void;
- auto registration() -> lv_indev_t* { return registration_; }
auto lock(bool l) -> void { is_locked_ = l; }
auto pushHooks(lua_State* L) -> int;