From 8761b9d7e0e95eb420a7a3b3a32b157c0095f811 Mon Sep 17 00:00:00 2001 From: Totoo Date: Thu, 4 Jan 2024 17:36:31 +0100 Subject: [PATCH] Send ASCII chars from USB serial to selected widget (#1708) * Initial commit for keyboard emulation * Added on_keyboard to some widgets * TextEdit partly * Multi key send at once * Frequency control support * Fix encoder emulation * Add keyboard to geomap * More widgets --- firmware/application/event_m0.cpp | 12 +++ firmware/application/event_m0.hpp | 2 + firmware/application/irq_controls.cpp | 9 ++- firmware/application/ui/ui_freqlist.cpp | 22 ++++++ firmware/application/ui/ui_freqlist.hpp | 2 + firmware/application/ui/ui_geomap.cpp | 7 ++ firmware/application/ui/ui_geomap.hpp | 1 + firmware/application/ui/ui_menu.cpp | 13 ++++ firmware/application/ui/ui_menu.hpp | 1 + firmware/application/ui/ui_receiver.cpp | 27 ++++++- firmware/application/ui/ui_receiver.hpp | 2 + firmware/application/usb_serial_shell.cpp | 39 ++++++++++ firmware/common/ui.hpp | 1 + firmware/common/ui_widget.cpp | 89 +++++++++++++++++++++++ firmware/common/ui_widget.hpp | 10 +++ 15 files changed, 232 insertions(+), 5 deletions(-) diff --git a/firmware/application/event_m0.cpp b/firmware/application/event_m0.cpp index 9124adfdc..a05c59a36 100644 --- a/firmware/application/event_m0.cpp +++ b/firmware/application/event_m0.cpp @@ -242,6 +242,18 @@ void EventDispatcher::emulateTouch(ui::TouchEvent event) { on_touch_event(event); } +void EventDispatcher::emulateKeyboard(ui::KeyboardEvent event) { + on_keyboard_event(event); +} + +void EventDispatcher::on_keyboard_event(ui::KeyboardEvent event) { + // send the key to focused widget, or parent if not accepts it + auto target = context.focus_manager().focus_widget(); + while ((target != nullptr) && !target->on_keyboard(event)) { + target = target->parent(); + } +} + void EventDispatcher::on_touch_event(ui::TouchEvent event) { /* TODO: Capture widget receiving the Start event, send Move and * End events to the same widget. diff --git a/firmware/application/event_m0.hpp b/firmware/application/event_m0.hpp index d6ae1a109..1bf705ee0 100644 --- a/firmware/application/event_m0.hpp +++ b/firmware/application/event_m0.hpp @@ -87,6 +87,7 @@ class EventDispatcher { } void emulateTouch(ui::TouchEvent event); + void emulateKeyboard(ui::KeyboardEvent event); private: static Thread* thread_event_loop; @@ -113,6 +114,7 @@ class EventDispatcher { ui::Widget* captured_widget{nullptr}; void on_touch_event(ui::TouchEvent event); + void on_keyboard_event(ui::KeyboardEvent event); // void blink_timer(); void handle_lcd_frame_sync(); diff --git a/firmware/application/irq_controls.cpp b/firmware/application/irq_controls.cpp index 08ff13c0a..68994caca 100644 --- a/firmware/application/irq_controls.cpp +++ b/firmware/application/irq_controls.cpp @@ -171,12 +171,13 @@ static bool encoder_update(const uint8_t raw) { } static bool encoder_read() { - const auto delta = encoder.update( - encoder_debounce[0].state() | (injected_encoder == 1), - encoder_debounce[1].state() | (injected_encoder == 2)); + auto delta = encoder.update(encoder_debounce[0].state(), encoder_debounce[1].state()); - if (injected_encoder > 0) + if (injected_encoder > 0) { + if (injected_encoder == 1) delta = -1; + if (injected_encoder == 2) delta = 1; injected_encoder = 0; + } if (delta != 0) { encoder_position += delta; diff --git a/firmware/application/ui/ui_freqlist.cpp b/firmware/application/ui/ui_freqlist.cpp index 9db0f3ab5..76280be96 100644 --- a/firmware/application/ui/ui_freqlist.cpp +++ b/firmware/application/ui/ui_freqlist.cpp @@ -92,6 +92,28 @@ void FreqManUIList::on_blur() { set_dirty(); } +bool FreqManUIList::on_keyboard(const KeyboardEvent key) { + if (!db_ || db_->empty()) + return false; + + auto delta = 0; + if (key == '-' && get_index() > 0) delta = -1; + if (key == '+' && get_index() < db_->entry_count() - 1) delta = 1; + if (delta != 0) { + adjust_selected_index(delta); + set_dirty(); + return true; + } + if (key == 10) { + if (on_select) { + on_select(get_index()); + return true; + } + } + + return false; +} + bool FreqManUIList::on_key(const KeyEvent key) { if (!db_ || db_->empty()) return false; diff --git a/firmware/application/ui/ui_freqlist.hpp b/firmware/application/ui/ui_freqlist.hpp index de09aa139..f01a0c853 100644 --- a/firmware/application/ui/ui_freqlist.hpp +++ b/firmware/application/ui/ui_freqlist.hpp @@ -50,6 +50,8 @@ class FreqManUIList : public Widget { void on_blur() override; bool on_key(const KeyEvent key) override; bool on_encoder(EncoderEvent delta) override; + bool on_keyboard(const KeyboardEvent event) override; + void set_parent_rect(Rect new_parent_rect) override; void set_index(size_t index); diff --git a/firmware/application/ui/ui_geomap.cpp b/firmware/application/ui/ui_geomap.cpp index edf3d7581..b3046f971 100644 --- a/firmware/application/ui/ui_geomap.cpp +++ b/firmware/application/ui/ui_geomap.cpp @@ -293,6 +293,13 @@ void GeoMap::paint(Painter& painter) { } } +bool GeoMap::on_keyboard(KeyboardEvent key) { + if (key == '+' || key == ' ') return on_encoder(1); + if (key == '-') return on_encoder(-1); + + return false; +} + bool GeoMap::on_touch(const TouchEvent event) { if ((event.type == TouchEvent::Type::Start) && (mode_ == PROMPT)) { set_highlighted(true); diff --git a/firmware/application/ui/ui_geomap.hpp b/firmware/application/ui/ui_geomap.hpp index 9a6833e14..a59abe97a 100644 --- a/firmware/application/ui/ui_geomap.hpp +++ b/firmware/application/ui/ui_geomap.hpp @@ -162,6 +162,7 @@ class GeoMap : public Widget { bool on_touch(const TouchEvent event) override; bool on_encoder(const EncoderEvent delta) override; + bool on_keyboard(const KeyboardEvent event) override; bool init(); void set_mode(GeoMapMode mode); diff --git a/firmware/application/ui/ui_menu.cpp b/firmware/application/ui/ui_menu.cpp index 28ae18673..0f73fe55e 100644 --- a/firmware/application/ui/ui_menu.cpp +++ b/firmware/application/ui/ui_menu.cpp @@ -269,6 +269,19 @@ bool MenuView::on_key(const KeyEvent key) { } } +bool MenuView::on_keyboard(const KeyboardEvent key) { + if (key == '-') return set_highlighted(highlighted_item - 1); + if (key == '+') return set_highlighted(highlighted_item + 1); + if (key == 10) { + if (menu_items[highlighted_item].on_select) { + menu_items[highlighted_item].on_select(KeyEvent::Right); + } + return true; + } + + return false; +} + bool MenuView::on_encoder(const EncoderEvent event) { set_highlighted(highlighted_item + event); return true; diff --git a/firmware/application/ui/ui_menu.hpp b/firmware/application/ui/ui_menu.hpp index 65364d66e..808d2001e 100644 --- a/firmware/application/ui/ui_menu.hpp +++ b/firmware/application/ui/ui_menu.hpp @@ -97,6 +97,7 @@ class MenuView : public View { void on_blur() override; bool on_key(const KeyEvent event) override; bool on_encoder(const EncoderEvent event) override; + bool on_keyboard(const KeyboardEvent event) override; private: void update_items(); diff --git a/firmware/application/ui/ui_receiver.cpp b/firmware/application/ui/ui_receiver.cpp index 24756f827..e79e2abd5 100644 --- a/firmware/application/ui/ui_receiver.cpp +++ b/firmware/application/ui/ui_receiver.cpp @@ -165,12 +165,18 @@ bool FrequencyField::on_key(KeyEvent event) { return false; } +bool FrequencyField::on_keyboard(KeyboardEvent key) { + if (key == '+' || key == ' ') return on_encoder(1); + if (key == '-') return on_encoder(-1); + + return false; +} + bool FrequencyField::on_encoder(const EncoderEvent delta) { if (digit_mode_) set_value(value_ + (delta * digit_step())); else set_value(value_ + (delta * step_)); - return true; } @@ -321,6 +327,25 @@ void FrequencyKeypadView::on_button(Button& button) { update_text(); } +bool FrequencyKeypadView::on_keyboard(const KeyboardEvent key) { + if (key == 8) { + digit_delete(); + update_text(); + return true; + } + if (key >= '0' && key <= '9') { + digit_add(key); + update_text(); + return true; + } + if (key == '.') { + field_toggle(); + update_text(); + return true; + } + return false; +} + void FrequencyKeypadView::digit_add(const char c) { if (state == State::DigitMHz) { if (clear_field_if_digits_entered) { diff --git a/firmware/application/ui/ui_receiver.hpp b/firmware/application/ui/ui_receiver.hpp index b123700cd..4652bc5b2 100644 --- a/firmware/application/ui/ui_receiver.hpp +++ b/firmware/application/ui/ui_receiver.hpp @@ -61,6 +61,7 @@ class FrequencyField : public Widget { bool on_key(KeyEvent event) override; bool on_encoder(EncoderEvent delta) override; + bool on_keyboard(KeyboardEvent key) override; bool on_touch(TouchEvent event) override; void on_focus() override; void on_blur() override; @@ -202,6 +203,7 @@ class FrequencyKeypadView : public View { rf::Frequency value() const; void set_value(const rf::Frequency new_value); bool on_encoder(const EncoderEvent delta) override; + bool on_keyboard(const KeyboardEvent key) override; private: int16_t focused_button = 0; diff --git a/firmware/application/usb_serial_shell.cpp b/firmware/application/usb_serial_shell.cpp index 842429678..a3267611b 100644 --- a/firmware/application/usb_serial_shell.cpp +++ b/firmware/application/usb_serial_shell.cpp @@ -365,6 +365,44 @@ static void cmd_touch(BaseSequentialStream* chp, int argc, char* argv[]) { chprintf(chp, "ok\r\n"); } +// send ascii keys in 2 char hex representation. Can send multiple keys at once like: keyboard 414243 (this will be ABC) +static void cmd_keyboard(BaseSequentialStream* chp, int argc, char* argv[]) { + if (argc != 1) { + chprintf(chp, "usage: keyboard XX\r\n"); + return; + } + + auto evtd = getEventDispatcherInstance(); + if (evtd == NULL) { + chprintf(chp, "error\r\n"); + } + + size_t data_string_len = strlen(argv[0]); + if (data_string_len % 2 != 0) { + chprintf(chp, "usage: keyboard XXXX\r\n"); + return; + } + + for (size_t i = 0; i < data_string_len; i++) { + char c = argv[0][i]; + if ((c < '0' || c > '9') && (c < 'A' || c > 'F')) { + chprintf(chp, "usage: keyboard XX\r\n"); + return; + } + } + + char buffer[3] = {0, 0, 0}; + + for (size_t i = 0; i < data_string_len / 2; i++) { + buffer[0] = argv[0][i * 2]; + buffer[1] = argv[0][i * 2 + 1]; + uint8_t chr = (uint8_t)strtol(buffer, NULL, 16); + evtd->emulateKeyboard(chr); + } + + chprintf(chp, "ok\r\n"); +} + static void cmd_sd_list_dir(BaseSequentialStream* chp, int argc, char* argv[]) { if (argc != 1) { chprintf(chp, "usage: ls /\r\n"); @@ -866,6 +904,7 @@ static const ShellCommand commands[] = { {"read_memory", cmd_read_memory}, {"button", cmd_button}, {"touch", cmd_touch}, + {"keyboard", cmd_keyboard}, {"ls", cmd_sd_list_dir}, {"rm", cmd_sd_delete}, {"open", cmd_sd_open}, diff --git a/firmware/common/ui.hpp b/firmware/common/ui.hpp index 06636c9bd..2f4890b42 100644 --- a/firmware/common/ui.hpp +++ b/firmware/common/ui.hpp @@ -371,6 +371,7 @@ enum class KeyEvent : uint8_t { }; using EncoderEvent = int32_t; +using KeyboardEvent = uint8_t; struct TouchEvent { enum class Type : uint32_t { diff --git a/firmware/common/ui_widget.cpp b/firmware/common/ui_widget.cpp index 675fdb9d4..2b42b19ac 100644 --- a/firmware/common/ui_widget.cpp +++ b/firmware/common/ui_widget.cpp @@ -172,6 +172,10 @@ bool Widget::on_touch(const TouchEvent event) { (void)event; return false; } +bool Widget::on_keyboard(const KeyboardEvent event) { + (void)event; + return false; +} const std::vector& Widget::children() const { return no_children; @@ -853,6 +857,11 @@ bool Checkbox::on_key(const KeyEvent key) { return false; } +bool Checkbox::on_keyboard(const KeyboardEvent event) { + if (event == 10 || event == 32) return set_value(not value_); + return false; +} + bool Checkbox::on_touch(const TouchEvent event) { switch (event.type) { case TouchEvent::Type::Start: @@ -944,6 +953,16 @@ bool Button::on_key(const KeyEvent key) { return false; } +bool Button::on_keyboard(const KeyboardEvent event) { + if (event == 10 || event == 32) { + if (on_select) { + on_select(*this); + return true; + } + } + return false; +} + bool Button::on_touch(const TouchEvent event) { switch (event.type) { case TouchEvent::Type::Start: @@ -1080,6 +1099,16 @@ bool ButtonWithEncoder::on_key(const KeyEvent key) { return false; } +bool ButtonWithEncoder::on_keyboard(const KeyboardEvent key) { + if (key == 32 || key == 10) { + if (on_select) { + on_select(*this); + return true; + } + } + return false; +} + bool ButtonWithEncoder::on_touch(const TouchEvent event) { switch (event.type) { case TouchEvent::Type::Start: @@ -1274,6 +1303,16 @@ bool NewButton::on_key(const KeyEvent key) { return false; } +bool NewButton::on_keyboard(const KeyboardEvent key) { + if (key == 32 || key == 10) { + if (on_select) { + on_select(); + return true; + } + } + return false; +} + bool NewButton::on_touch(const TouchEvent event) { switch (event.type) { case TouchEvent::Type::Start: @@ -1371,6 +1410,16 @@ bool ImageButton::on_key(const KeyEvent key) { return false; } +bool ImageButton::on_keyboard(const KeyboardEvent key) { + if (key == 32 || key == 10) { + if (on_select) { + on_select(*this); + return true; + } + } + return false; +} + bool ImageButton::on_touch(const TouchEvent event) { switch (event.type) { case TouchEvent::Type::Start: @@ -1537,6 +1586,12 @@ bool ImageOptionsField::on_encoder(const EncoderEvent delta) { return true; } +bool ImageOptionsField::on_keyboard(const KeyboardEvent key) { + if (key == '+' || key == ' ' || key == 10) return on_encoder(1); + if (key == '-' || key == 8) return on_encoder(-1); + return false; +} + bool ImageOptionsField::on_touch(const TouchEvent event) { if (event.type == TouchEvent::Type::Start) { focus(); @@ -1653,6 +1708,11 @@ bool OptionsField::on_encoder(const EncoderEvent delta) { set_selected_index(new_value); return true; } +bool OptionsField::on_keyboard(const KeyboardEvent key) { + if (key == '+' || key == ' ' || key == 10) return on_encoder(1); + if (key == '-' || key == 8) return on_encoder(-1); + return false; +} bool OptionsField::on_touch(const TouchEvent event) { if (event.type == TouchEvent::Type::Start) { @@ -1772,6 +1832,19 @@ bool TextEdit::on_key(const KeyEvent key) { return true; } +bool TextEdit::on_keyboard(const KeyboardEvent key) { + // if ascii printable + if (key >= 0x20 && key <= 0x7e) { + char_add(key); + return true; + } + if (key == 8) { + char_delete(); + return true; + } + return false; +} + bool TextEdit::on_encoder(const EncoderEvent delta) { int32_t new_pos = cursor_pos_ + delta; @@ -1928,6 +2001,22 @@ bool NumberField::on_encoder(const EncoderEvent delta) { return true; } +bool NumberField::on_keyboard(const KeyboardEvent key) { + if (key == 10) { + if (on_select) { + on_select(*this); + return true; + } + } + if (key == '+' || key == ' ') { + return on_encoder(1); + } + if (key == '-' || key == 8) { + return on_encoder(-1); + } + return false; +} + bool NumberField::on_touch(const TouchEvent event) { if (event.type == TouchEvent::Type::Start) { focus(); diff --git a/firmware/common/ui_widget.hpp b/firmware/common/ui_widget.hpp index 5359eecfc..ff80bc164 100644 --- a/firmware/common/ui_widget.hpp +++ b/firmware/common/ui_widget.hpp @@ -101,6 +101,7 @@ class Widget { virtual bool on_key(const KeyEvent event); virtual bool on_encoder(const EncoderEvent event); virtual bool on_touch(const TouchEvent event); + virtual bool on_keyboard(const KeyboardEvent event); virtual const std::vector& children() const; virtual Context& context() const; @@ -382,6 +383,7 @@ class Checkbox : public Widget { void paint(Painter& painter) override; bool on_key(const KeyEvent key) override; + bool on_keyboard(const KeyboardEvent key) override; bool on_touch(const TouchEvent event) override; private: @@ -418,6 +420,7 @@ class Button : public Widget { void on_focus() override; bool on_key(const KeyEvent key) override; bool on_touch(const TouchEvent event) override; + bool on_keyboard(const KeyboardEvent event) override; private: std::string text_; @@ -456,6 +459,7 @@ class ButtonWithEncoder : public Widget { bool on_key(const KeyEvent key) override; bool on_touch(const TouchEvent event) override; bool on_encoder(const EncoderEvent delta) override; + bool on_keyboard(const KeyboardEvent event) override; private: std::string text_; @@ -490,6 +494,7 @@ class NewButton : public Widget { void on_focus() override; bool on_key(const KeyEvent key) override; bool on_touch(const TouchEvent event) override; + bool on_keyboard(const KeyboardEvent event) override; void paint(Painter& painter) override; @@ -544,6 +549,7 @@ class ImageButton : public Image { bool on_key(const KeyEvent key) override; bool on_touch(const TouchEvent event) override; + bool on_keyboard(const KeyboardEvent event) override; }; /* A button that toggles between two images when set. */ @@ -621,6 +627,7 @@ class ImageOptionsField : public Widget { void on_focus() override; bool on_encoder(const EncoderEvent delta) override; bool on_touch(const TouchEvent event) override; + bool on_keyboard(const KeyboardEvent event) override; private: options_t options; @@ -658,6 +665,7 @@ class OptionsField : public Widget { void on_focus() override; bool on_encoder(const EncoderEvent delta) override; bool on_touch(const TouchEvent event) override; + bool on_keyboard(const KeyboardEvent event) override; private: const size_t length_; @@ -702,6 +710,7 @@ class TextEdit : public Widget { bool on_key(const KeyEvent key) override; bool on_encoder(const EncoderEvent delta) override; bool on_touch(const TouchEvent event) override; + bool on_keyboard(const KeyboardEvent event) override; void on_focus() override; void on_blur() override; @@ -763,6 +772,7 @@ class NumberField : public Widget { bool on_key(const KeyEvent key) override; bool on_encoder(const EncoderEvent delta) override; bool on_touch(const TouchEvent event) override; + bool on_keyboard(const KeyboardEvent event) override; private: range_t range;