Просмотр исходного кода

Also use an RPC_KEYBOARD if present.

Derek Jamison 1 год назад
Родитель
Сommit
8b452eadc6
3 измененных файлов с 572 добавлено и 52 удалено
  1. 162 0
      text_input/rpc_keyboard.h
  2. 150 0
      text_input/rpc_keyboard_stub.c
  3. 260 52
      text_input/uart_text_input.c

+ 162 - 0
text_input/rpc_keyboard.h

@@ -0,0 +1,162 @@
+#pragma once
+
+#include <core/common_defines.h>
+#include <core/mutex.h>
+#include <core/pubsub.h>
+
+#define RECORD_RPC_KEYBOARD "rpckeyboard"
+
+#define RPC_KEYBOARD_KEY_RIGHT '\x13'
+#define RPC_KEYBOARD_KEY_LEFT '\x14'
+#define RPC_KEYBOARD_KEY_ENTER '\x0D'
+#define RPC_KEYBOARD_KEY_BACKSPACE '\x08'
+
+typedef enum
+{
+    // Unknown error occurred
+    RpcKeyboardChatpadStatusError,
+    // The chatpad worker is stopped
+    RpcKeyboardChatpadStatusStopped,
+    // The chatpad worker is started, but not ready
+    RpcKeyboardChatpadStatusStarted,
+    // The chatpad worker is ready and got response from chatpad
+    RpcKeyboardChatpadStatusReady,
+} RpcKeyboardChatpadStatus;
+
+typedef struct RpcKeyboard RpcKeyboard;
+
+typedef enum
+{
+    // Replacement text was provided by the user
+    RpcKeyboardEventTypeTextEntered,
+    // A single character was provided by the user
+    RpcKeyboardEventTypeCharEntered,
+    // A macro was entered by the user
+    RpcKeyboardEventTypeMacroEntered,
+} RpcKeyboardEventType;
+
+typedef struct
+{
+    // The mutex to protect the data, call furi_mutex_acquire/furi_mutex_release.
+    FuriMutex *mutex;
+    // The text message, macro or character.
+    char message[256];
+    // The length of the message.
+    uint16_t length;
+    // The newline enabled flag, allow newline to submit text.
+    bool newline_enabled;
+} RpcKeyboardEventData;
+
+typedef struct
+{
+    RpcKeyboardEventType type;
+    RpcKeyboardEventData data;
+} RpcKeyboardEvent;
+
+typedef FuriPubSub *(*RpcKeyboardGetPubsub)(RpcKeyboard *rpc_keyboard);
+typedef void (*RpcKeyboardNewlineEnable)(RpcKeyboard *rpc_keyboard, bool enable);
+typedef void (*RpcKeyboardPublishCharFn)(RpcKeyboard *keyboard, char character);
+typedef void (*RpcKeyboardPublishMacroFn)(RpcKeyboard *rpc_keyboard, char macro);
+typedef char *(*RpcKeyboardGetMacroFn)(RpcKeyboard *rpc_keyboard, char macro);
+typedef void (*RpcKeyboardSetMacroFn)(RpcKeyboard *rpc_keyboard, char macro, char *value);
+typedef void (*RpcKeyboardChatpadStartFn)(RpcKeyboard *rpc_keyboard);
+typedef void (*RpcKeyboardChatpadStopFn)(RpcKeyboard *rpc_keyboard);
+typedef RpcKeyboardChatpadStatus (*RpcKeyboardChatpadStatusFn)(RpcKeyboard *rpc_keyboard);
+
+typedef struct RpcKeyboardFunctions RpcKeyboardFunctions;
+struct RpcKeyboardFunctions
+{
+    uint16_t major;
+    uint16_t minor;
+    RpcKeyboardGetPubsub fn_get_pubsub;
+    RpcKeyboardNewlineEnable fn_newline_enable;
+    RpcKeyboardPublishCharFn fn_publish_char;
+    RpcKeyboardPublishMacroFn fn_publish_macro;
+    RpcKeyboardGetMacroFn fn_get_macro;
+    RpcKeyboardSetMacroFn fn_set_macro;
+    RpcKeyboardChatpadStartFn fn_chatpad_start;
+    RpcKeyboardChatpadStopFn fn_chatpad_stop;
+    RpcKeyboardChatpadStatusFn fn_chatpad_status;
+};
+
+/**
+ * @brief STARTUP - Register the remote keyboard.
+ */
+void rpc_keyboard_register(void);
+
+/**
+ * @brief UNUSED - Unregister the remote keyboard.
+ */
+void rpc_keyboard_release(void);
+
+/**
+ * @brief Get the pubsub object for the remote keyboard.
+ * @details This function returns the pubsub object, use to subscribe to keyboard events.
+ * @param[in] rpc_keyboard pointer to the RECORD_RPC_KEYBOARD.
+ * @return FuriPubSub* pointer to the pubsub object.
+ */
+FuriPubSub *rpc_keyboard_get_pubsub(RpcKeyboard *rpc_keyboard);
+
+/**
+ * @brief Enable or disable newline character submitting the text.
+ * @param[in] rpc_keyboard pointer to the RECORD_RPC_KEYBOARD.
+ * @param[in] enable true to enable, false to disable.
+ */
+void rpc_keyboard_newline_enable(RpcKeyboard *rpc_keyboard, bool enable);
+
+/**
+ * @brief Publish the replacement text to the remote keyboard.
+ * @param[in] rpc_keyboard pointer to the RECORD_RPC_KEYBOARD.
+ * @param[in] bytes pointer to the text buffer.
+ * @param[in] buffer_size size of the text buffer.
+ */
+void rpc_keyboard_publish_text(RpcKeyboard *rpc_keyboard, uint8_t *bytes, uint32_t buffer_size);
+
+/**
+ * @brief Publish a single key pressed on the remote keyboard.
+ * @param[in] rpc_keyboard pointer to the RECORD_RPC_KEYBOARD.
+ * @param[in] character the character that was pressed.
+ */
+void rpc_keyboard_publish_char(RpcKeyboard *rpc_keyboard, char character);
+
+/**
+ * @brief Publish a macro key pressed on the remote keyboard.
+ * @param[in] rpc_keyboard pointer to the RECORD_RPC_KEYBOARD.
+ * @param[in] character the macro key that was pressed.
+ */
+void rpc_keyboard_publish_macro(RpcKeyboard *rpc_keyboard, char macro);
+
+/**
+ * @brief Get the macro text associated with a macro key.
+ * @param[in] rpc_keyboard pointer to the RECORD_RPC_KEYBOARD.
+ * @param[in] macro the macro key.
+ * @return char* pointer to the macro text. NULL if the macro key is not set. User must free the memory.
+ */
+char *rpc_keyboard_get_macro(RpcKeyboard *rpc_keyboard, char macro);
+
+/**
+ * @brief Set the macro text associated with a macro key.
+ * @param[in] rpc_keyboard pointer to the RECORD_RPC_KEYBOARD.
+ * @param[in] macro the macro key.
+ * @param[in] value the macro text.
+ */
+void rpc_keyboard_set_macro(RpcKeyboard *rpc_keyboard, char macro, char *value);
+
+/**
+ * @brief Initializes the chatpad and starts listening for keypresses.
+ * @param[in] rpc_keyboard pointer to the RECORD_RPC_KEYBOARD.
+ */
+void rpc_keyboard_chatpad_start(RpcKeyboard *rpc_keyboard);
+
+/**
+ * @brief Stops the chatpad & frees resources.
+ * @param[in] rpc_keyboard pointer to the RECORD_RPC_KEYBOARD.
+ */
+void rpc_keyboard_chatpad_stop(RpcKeyboard *rpc_keyboard);
+
+/**
+ * @brief Get the status of the chatpad.
+ * @param[in] rpc_keyboard pointer to the RECORD_RPC_KEYBOARD.
+ * @return RpcKeyboardChatpadStatus the status of the chatpad.
+ */
+RpcKeyboardChatpadStatus rpc_keyboard_chatpad_status(RpcKeyboard *rpc_keyboard);

+ 150 - 0
text_input/rpc_keyboard_stub.c

@@ -0,0 +1,150 @@
+#include "rpc_keyboard.h"
+
+#include <furi.h>
+
+static bool rpc_keyboard_functions_check_version(RpcKeyboardFunctions *stub)
+{
+    furi_check(stub);
+    if (stub->major == 1 && stub->minor > 2)
+    {
+        return true;
+    }
+    FURI_LOG_D("RpcKeyboard", "Unsupported version %d.%d", stub->major, stub->minor);
+    return false;
+}
+
+/**
+ * @brief Get the pubsub object for the remote keyboard.
+ * @details This function returns the pubsub object, use to subscribe to keyboard events.
+ * @param[in] rpc_keyboard pointer to the RECORD_RPC_KEYBOARD.
+ * @return FuriPubSub* pointer to the pubsub object.
+ */
+FuriPubSub *rpc_keyboard_get_pubsub(RpcKeyboard *rpc_keyboard)
+{
+    RpcKeyboardFunctions *stub = (RpcKeyboardFunctions *)rpc_keyboard;
+    if (!rpc_keyboard_functions_check_version(stub))
+    {
+        return NULL;
+    }
+    return stub->fn_get_pubsub((RpcKeyboard *)rpc_keyboard);
+}
+
+/**
+ * @brief Enable or disable newline character submitting the text.
+ * @param[in] rpc_keyboard pointer to the RECORD_RPC_KEYBOARD.
+ * @param[in] enable true to enable, false to disable.
+ */
+void rpc_keyboard_newline_enable(RpcKeyboard *rpc_keyboard, bool enable)
+{
+    RpcKeyboardFunctions *stub = (RpcKeyboardFunctions *)rpc_keyboard;
+    if (!rpc_keyboard_functions_check_version(stub))
+    {
+        return;
+    }
+    stub->fn_newline_enable((RpcKeyboard *)rpc_keyboard, enable);
+}
+
+/**
+ * @brief Publish a single key pressed on the remote keyboard.
+ * @param[in] rpc_keyboard pointer to the RECORD_RPC_KEYBOARD.
+ * @param[in] character the character that was pressed.
+ */
+void rpc_keyboard_publish_char(RpcKeyboard *rpc_keyboard, char character)
+{
+    RpcKeyboardFunctions *stub = (RpcKeyboardFunctions *)rpc_keyboard;
+    if (!rpc_keyboard_functions_check_version(stub))
+    {
+        return;
+    }
+    stub->fn_publish_char((RpcKeyboard *)rpc_keyboard, character);
+}
+
+/**
+ * @brief Publish a macro key pressed on the remote keyboard.
+ * @param[in] rpc_keyboard pointer to the RECORD_RPC_KEYBOARD.
+ * @param[in] character the macro key that was pressed.
+ */
+void rpc_keyboard_publish_macro(RpcKeyboard *rpc_keyboard, char macro)
+{
+    RpcKeyboardFunctions *stub = (RpcKeyboardFunctions *)rpc_keyboard;
+    if (!rpc_keyboard_functions_check_version(stub))
+    {
+        return;
+    }
+    stub->fn_publish_macro((RpcKeyboard *)rpc_keyboard, macro);
+}
+
+/**
+ * @brief Get the macro text associated with a macro key.
+ * @param[in] rpc_keyboard pointer to the RECORD_RPC_KEYBOARD.
+ * @param[in] macro the macro key.
+ * @return char* pointer to the macro text. NULL if the macro key is not set. User must free the memory.
+ */
+char *rpc_keyboard_get_macro(RpcKeyboard *rpc_keyboard, char macro)
+{
+    RpcKeyboardFunctions *stub = (RpcKeyboardFunctions *)rpc_keyboard;
+    if (!rpc_keyboard_functions_check_version(stub))
+    {
+        return NULL;
+    }
+    return stub->fn_get_macro((RpcKeyboard *)rpc_keyboard, macro);
+}
+
+/**
+ * @brief Set the macro text associated with a macro key.
+ * @param[in] rpc_keyboard pointer to the RECORD_RPC_KEYBOARD.
+ * @param[in] macro the macro key.
+ * @param[in] value the macro text.
+ */
+void rpc_keyboard_set_macro(RpcKeyboard *rpc_keyboard, char macro, char *value)
+{
+    RpcKeyboardFunctions *stub = (RpcKeyboardFunctions *)rpc_keyboard;
+    if (!rpc_keyboard_functions_check_version(stub))
+    {
+        return;
+    }
+    stub->fn_set_macro((RpcKeyboard *)rpc_keyboard, macro, value);
+}
+
+/**
+ * @brief Initializes the chatpad and starts listening for keypresses.
+ * @param[in] rpc_keyboard pointer to the RECORD_RPC_KEYBOARD.
+ */
+void rpc_keyboard_chatpad_start(RpcKeyboard *rpc_keyboard)
+{
+    RpcKeyboardFunctions *stub = (RpcKeyboardFunctions *)rpc_keyboard;
+    if (!rpc_keyboard_functions_check_version(stub))
+    {
+        return;
+    }
+    stub->fn_chatpad_start((RpcKeyboard *)rpc_keyboard);
+}
+
+/**
+ * @brief Stops the chatpad & frees resources.
+ * @param[in] rpc_keyboard pointer to the RECORD_RPC_KEYBOARD.
+ */
+void rpc_keyboard_chatpad_stop(RpcKeyboard *rpc_keyboard)
+{
+    RpcKeyboardFunctions *stub = (RpcKeyboardFunctions *)rpc_keyboard;
+    if (!rpc_keyboard_functions_check_version(stub))
+    {
+        return;
+    }
+    stub->fn_chatpad_stop((RpcKeyboard *)rpc_keyboard);
+}
+
+/**
+ * @brief Get the status of the chatpad.
+ * @param[in] rpc_keyboard pointer to the RECORD_RPC_KEYBOARD.
+ * @return RpcKeyboardChatpadStatus the status of the chatpad.
+ */
+RpcKeyboardChatpadStatus rpc_keyboard_chatpad_status(RpcKeyboard *rpc_keyboard)
+{
+    RpcKeyboardFunctions *stub = (RpcKeyboardFunctions *)rpc_keyboard;
+    if (!rpc_keyboard_functions_check_version(stub))
+    {
+        return RpcKeyboardChatpadStatusError;
+    }
+    return stub->fn_chatpad_status((RpcKeyboard *)rpc_keyboard);
+}

+ 260 - 52
text_input/uart_text_input.c

@@ -4,6 +4,7 @@
 #include <gui/elements.h>
 #include <gui/elements.h>
 #include "flip_social_icons.h"
 #include "flip_social_icons.h"
 #include <furi.h>
 #include <furi.h>
+#include "rpc_keyboard.h"
 
 
 struct UART_TextInput
 struct UART_TextInput
 {
 {
@@ -25,6 +26,9 @@ typedef struct
     size_t text_buffer_size;
     size_t text_buffer_size;
     bool clear_default_text;
     bool clear_default_text;
 
 
+    FuriPubSubSubscription *keyboard_subscription;
+    bool invoke_callback;
+
     UART_TextInputCallback callback;
     UART_TextInputCallback callback;
     void *callback_context;
     void *callback_context;
 
 
@@ -281,6 +285,21 @@ static void uart_text_input_view_draw_callback(Canvas *canvas, void *_model)
     uint8_t needed_string_width = canvas_width(canvas) - 8;
     uint8_t needed_string_width = canvas_width(canvas) - 8;
     uint8_t start_pos = 4;
     uint8_t start_pos = 4;
 
 
+    if (model->invoke_callback)
+    {
+        model->invoke_callback = false;
+        if (model->validator_callback && (!model->validator_callback(model->text_buffer, model->validator_text, model->validator_callback_context)))
+        {
+            model->valadator_message_visible = true;
+        }
+        else if (model->callback != 0)
+        {
+            // We hijack the current thread to invoke the callback (we aren't doing a draw).
+            model->callback(model->callback_context);
+            return;
+        }
+    }
+
     const char *text = model->text_buffer;
     const char *text = model->text_buffer;
 
 
     canvas_clear(canvas);
     canvas_clear(canvas);
@@ -329,17 +348,17 @@ static void uart_text_input_view_draw_callback(Canvas *canvas, void *_model)
                 if (model->selected_row == row && model->selected_column == column)
                 if (model->selected_row == row && model->selected_column == column)
                 {
                 {
                     canvas_draw_icon(
                     canvas_draw_icon(
-                        canvas,
-                        keyboard_origin_x + keys[column].x,
+                        canvas, 
+                        keyboard_origin_x + keys[column].x, 
                         keyboard_origin_y + keys[column].y,
                         keyboard_origin_y + keys[column].y,
                         &I_KeySaveSelected_24x11);
                         &I_KeySaveSelected_24x11);
                 }
                 }
                 else
                 else
                 {
                 {
                     canvas_draw_icon(
                     canvas_draw_icon(
-                        canvas,
-                        keyboard_origin_x + keys[column].x,
-                        keyboard_origin_y + keys[column].y,
+                        canvas, 
+                        keyboard_origin_x + keys[column].x, 
+                        keyboard_origin_y + keys[column].y, 
                         &I_KeySave_24x11);
                         &I_KeySave_24x11);
                 }
                 }
             }
             }
@@ -349,17 +368,17 @@ static void uart_text_input_view_draw_callback(Canvas *canvas, void *_model)
                 if (model->selected_row == row && model->selected_column == column)
                 if (model->selected_row == row && model->selected_column == column)
                 {
                 {
                     canvas_draw_icon(
                     canvas_draw_icon(
-                        canvas,
-                        keyboard_origin_x + keys[column].x,
-                        keyboard_origin_y + keys[column].y,
+                        canvas, 
+                        keyboard_origin_x + keys[column].x, 
+                        keyboard_origin_y + keys[column].y, 
                         &I_KeyBackspaceSelected_16x9);
                         &I_KeyBackspaceSelected_16x9);
                 }
                 }
                 else
                 else
                 {
                 {
                     canvas_draw_icon(
                     canvas_draw_icon(
-                        canvas,
-                        keyboard_origin_x + keys[column].x,
-                        keyboard_origin_y + keys[column].y,
+                        canvas, 
+                        keyboard_origin_x + keys[column].x, 
+                        keyboard_origin_y + keys[column].y, 
                         &I_KeyBackspace_16x9);
                         &I_KeyBackspace_16x9);
                 }
                 }
             }
             }
@@ -369,10 +388,10 @@ static void uart_text_input_view_draw_callback(Canvas *canvas, void *_model)
                 {
                 {
                     canvas_set_color(canvas, ColorBlack);
                     canvas_set_color(canvas, ColorBlack);
                     canvas_draw_box(
                     canvas_draw_box(
-                        canvas,
-                        keyboard_origin_x + keys[column].x - 1,
-                        keyboard_origin_y + keys[column].y - 8,
-                        7,
+                        canvas, 
+                        keyboard_origin_x + keys[column].x - 1, 
+                        keyboard_origin_y + keys[column].y - 8, 
+                        7, 
                         10);
                         10);
                     canvas_set_color(canvas, ColorWhite);
                     canvas_set_color(canvas, ColorWhite);
                 }
                 }
@@ -383,17 +402,17 @@ static void uart_text_input_view_draw_callback(Canvas *canvas, void *_model)
                 if (0 == strcmp(model->header, mode_AT))
                 if (0 == strcmp(model->header, mode_AT))
                 {
                 {
                     canvas_draw_glyph(
                     canvas_draw_glyph(
-                        canvas,
-                        keyboard_origin_x + keys[column].x,
-                        keyboard_origin_y + keys[column].y,
+                        canvas, 
+                        keyboard_origin_x + keys[column].x, 
+                        keyboard_origin_y + keys[column].y, 
                         char_to_uppercase(keys[column].text));
                         char_to_uppercase(keys[column].text));
                 }
                 }
                 else
                 else
                 {
                 {
                     canvas_draw_glyph(
                     canvas_draw_glyph(
-                        canvas,
-                        keyboard_origin_x + keys[column].x,
-                        keyboard_origin_y + keys[column].y,
+                        canvas, 
+                        keyboard_origin_x + keys[column].x, 
+                        keyboard_origin_y + keys[column].y, 
                         keys[column].text);
                         keys[column].text);
                 }
                 }
             }
             }
@@ -413,7 +432,7 @@ static void uart_text_input_view_draw_callback(Canvas *canvas, void *_model)
     }
     }
 }
 }
 
 
-static void
+static void 
 uart_text_input_handle_up(UART_TextInput *uart_text_input, UART_TextInputModel *model)
 uart_text_input_handle_up(UART_TextInput *uart_text_input, UART_TextInputModel *model)
 {
 {
     UNUSED(uart_text_input);
     UNUSED(uart_text_input);
@@ -427,7 +446,7 @@ uart_text_input_handle_up(UART_TextInput *uart_text_input, UART_TextInputModel *
     }
     }
 }
 }
 
 
-static void
+static void 
 uart_text_input_handle_down(UART_TextInput *uart_text_input, UART_TextInputModel *model)
 uart_text_input_handle_down(UART_TextInput *uart_text_input, UART_TextInputModel *model)
 {
 {
     UNUSED(uart_text_input);
     UNUSED(uart_text_input);
@@ -441,7 +460,7 @@ uart_text_input_handle_down(UART_TextInput *uart_text_input, UART_TextInputModel
     }
     }
 }
 }
 
 
-static void
+static void 
 uart_text_input_handle_left(UART_TextInput *uart_text_input, UART_TextInputModel *model)
 uart_text_input_handle_left(UART_TextInput *uart_text_input, UART_TextInputModel *model)
 {
 {
     UNUSED(uart_text_input);
     UNUSED(uart_text_input);
@@ -455,7 +474,7 @@ uart_text_input_handle_left(UART_TextInput *uart_text_input, UART_TextInputModel
     }
     }
 }
 }
 
 
-static void
+static void 
 uart_text_input_handle_right(UART_TextInput *uart_text_input, UART_TextInputModel *model)
 uart_text_input_handle_right(UART_TextInput *uart_text_input, UART_TextInputModel *model)
 {
 {
     UNUSED(uart_text_input);
     UNUSED(uart_text_input);
@@ -470,8 +489,8 @@ uart_text_input_handle_right(UART_TextInput *uart_text_input, UART_TextInputMode
 }
 }
 
 
 static void uart_text_input_handle_ok(
 static void uart_text_input_handle_ok(
-    UART_TextInput *uart_text_input,
-    UART_TextInputModel *model,
+    UART_TextInput *uart_text_input, 
+    UART_TextInputModel *model, 
     bool shift)
     bool shift)
 {
 {
     char selected = get_selected_char(model);
     char selected = get_selected_char(model);
@@ -537,7 +556,7 @@ static bool uart_text_input_view_input_callback(InputEvent *event, void *context
     // Acquire model
     // Acquire model
     UART_TextInputModel *model = view_get_model(uart_text_input->view);
     UART_TextInputModel *model = view_get_model(uart_text_input->view);
 
 
-    if ((!(event->type == InputTypePress) && !(event->type == InputTypeRelease)) &&
+    if ((!(event->type == InputTypePress) && !(event->type == InputTypeRelease)) && 
         model->valadator_message_visible)
         model->valadator_message_visible)
     {
     {
         model->valadator_message_visible = false;
         model->valadator_message_visible = false;
@@ -634,12 +653,199 @@ void uart_text_input_timer_callback(void *context)
     UART_TextInput *uart_text_input = context;
     UART_TextInput *uart_text_input = context;
 
 
     with_view_model(
     with_view_model(
-        uart_text_input->view,
+        uart_text_input->view, 
+        UART_TextInputModel * model, 
+        { model->valadator_message_visible = false; }, 
+        true);
+}
+
+static void text_input_keyboard_callback_line(UART_TextInput *text_input, const RpcKeyboardEvent *event)
+{
+    with_view_model(
+        text_input->view,
+        UART_TextInputModel * model,
+        {
+            if (model->text_buffer != NULL && model->text_buffer_size > 0)
+            {
+                if (event->data.length > 0)
+                {
+                    furi_mutex_acquire(event->data.mutex, FuriWaitForever);
+                    size_t len = event->data.length;
+                    if (len >= model->text_buffer_size)
+                    {
+                        len = model->text_buffer_size - 1;
+                    }
+
+                    bool newline = false;
+                    bool substitutions = false;
+                    size_t copy_index = 0;
+                    for (size_t i = 0; i < len; i++)
+                    {
+                        char ch = event->data.message[i];
+                        if ((ch >= 0x20 && ch <= 0x7E) || ch == '\n' || ch == '\r')
+                        {
+                            model->text_buffer[copy_index++] = ch;
+                            if (ch == '\n' || ch == '\r')
+                            {
+                                newline = event->data.newline_enabled && !substitutions; // TODO: No min-length check?
+                                break;
+                            }
+                        }
+                    }
+                    model->text_buffer[copy_index] = '\0';
+                    furi_mutex_release(event->data.mutex);
+                    FURI_LOG_D("text_input", "copy: %d, %d, %s", len, copy_index, model->text_buffer);
+
+                    // Set focus on Save
+                    model->selected_row = 3;
+                    model->selected_column = 8;
+
+                    // Hijack the next draw to invoke the callback if newline is true.
+                    model->invoke_callback = newline;
+                }
+            }
+        },
+        true);
+}
+
+static void text_input_keyboard_type_key(UART_TextInput *text_input, char selected)
+{
+    with_view_model(
+        text_input->view,
         UART_TextInputModel * model,
         UART_TextInputModel * model,
-        { model->valadator_message_visible = false; },
+        {
+            size_t text_length = strlen(model->text_buffer);
+            char search_key = isupper(selected) ? tolower(selected) : selected == ' ' ? '_'
+                                                                                      : selected;
+            bool found = false;
+            for (int row = 0; row < keyboard_row_count; row++)
+            {
+                const UART_TextInputKey *keys = get_row(row);
+                for (int column = 0; column < get_row_size(row); column++)
+                {
+                    if (keys[column].text == search_key)
+                    {
+                        model->selected_row = row;
+                        model->selected_column = column;
+                        found = true;
+                    }
+                }
+            }
+            if (!found)
+            {
+                // Set focus on Backspace
+                model->selected_row = 2;
+                model->selected_column = 9;
+            }
+
+            if (selected == ENTER_KEY)
+            {
+                if (model->validator_callback && (!model->validator_callback(model->text_buffer, model->validator_text, model->validator_callback_context)))
+                {
+                    model->valadator_message_visible = true;
+                    furi_timer_start(text_input->timer, furi_kernel_get_tick_frequency() * 4);
+                }
+                else if (model->callback != 0)
+                { // TODO: no min-length check
+                    model->callback(model->callback_context);
+                }
+            }
+            else if (selected == BACKSPACE_KEY)
+            {
+                uart_text_input_backspace_cb(model);
+            }
+            else
+            {
+                if (model->clear_default_text)
+                {
+                    text_length = 0;
+                }
+                if (selected == RPC_KEYBOARD_KEY_LEFT || selected == RPC_KEYBOARD_KEY_RIGHT)
+                {
+                    // ignore these keys for now
+                }
+                else if (text_length < (model->text_buffer_size - 1))
+                {
+                    model->text_buffer[text_length] = selected;
+                    model->text_buffer[text_length + 1] = 0;
+                }
+            }
+            model->clear_default_text = false;
+        },
         true);
         true);
 }
 }
 
 
+static void text_input_keyboard_callback(const void *message, void *context)
+{
+    UART_TextInput *text_input = context;
+    const RpcKeyboardEvent *event = message;
+
+    if (event == NULL)
+    {
+        return;
+    }
+
+    switch (event->type)
+    {
+    case RpcKeyboardEventTypeTextEntered:
+        text_input_keyboard_callback_line(text_input, event);
+        break;
+    case RpcKeyboardEventTypeCharEntered:
+        char ch = event->data.message[0];
+        FURI_LOG_I("text_input", "char: %c", ch);
+        text_input_keyboard_type_key(text_input, ch);
+        break;
+    case RpcKeyboardEventTypeMacroEntered:
+        furi_mutex_acquire(event->data.mutex, FuriWaitForever);
+        FURI_LOG_I("text_input", "macro: %s", event->data.message);
+        for (size_t i = 0; i < event->data.length; i++)
+        {
+            text_input_keyboard_type_key(text_input, event->data.message[i]);
+        }
+        furi_mutex_release(event->data.mutex);
+        break;
+    }
+}
+
+static void text_input_view_enter_callback(void *context)
+{
+    furi_assert(context);
+    UART_TextInput *text_input = context;
+    if (furi_record_exists(RECORD_RPC_KEYBOARD))
+    {
+        RpcKeyboard *rpc_keyboard = furi_record_open(RECORD_RPC_KEYBOARD);
+        FuriPubSub *rpc_keyboard_pubsub = rpc_keyboard_get_pubsub(rpc_keyboard);
+        if (rpc_keyboard_pubsub != NULL)
+        {
+            with_view_model(text_input->view, UART_TextInputModel * model, { model->keyboard_subscription = furi_pubsub_subscribe(rpc_keyboard_pubsub, text_input_keyboard_callback, text_input); }, false);
+        }
+        furi_record_close(RECORD_RPC_KEYBOARD);
+    }
+}
+
+static void text_input_view_exit_callback(void *context)
+{
+    furi_assert(context);
+    UART_TextInput *text_input = context;
+    if (furi_record_exists(RECORD_RPC_KEYBOARD))
+    {
+        RpcKeyboard *rpc_keyboard = furi_record_open(RECORD_RPC_KEYBOARD);
+        FuriPubSub *rpc_keyboard_pubsub = rpc_keyboard_get_pubsub(rpc_keyboard);
+        if (rpc_keyboard_pubsub != NULL)
+        {
+            with_view_model(
+                text_input->view,
+                UART_TextInputModel * model,
+                {
+                    furi_pubsub_unsubscribe(rpc_keyboard_pubsub, model->keyboard_subscription);
+                    model->keyboard_subscription = NULL;
+                },
+                false);
+        }
+        furi_record_close(RECORD_RPC_KEYBOARD);
+    }
+}
+
 UART_TextInput *uart_text_input_alloc()
 UART_TextInput *uart_text_input_alloc()
 {
 {
     UART_TextInput *uart_text_input = malloc(sizeof(UART_TextInput));
     UART_TextInput *uart_text_input = malloc(sizeof(UART_TextInput));
@@ -648,14 +854,16 @@ UART_TextInput *uart_text_input_alloc()
     view_allocate_model(uart_text_input->view, ViewModelTypeLocking, sizeof(UART_TextInputModel));
     view_allocate_model(uart_text_input->view, ViewModelTypeLocking, sizeof(UART_TextInputModel));
     view_set_draw_callback(uart_text_input->view, uart_text_input_view_draw_callback);
     view_set_draw_callback(uart_text_input->view, uart_text_input_view_draw_callback);
     view_set_input_callback(uart_text_input->view, uart_text_input_view_input_callback);
     view_set_input_callback(uart_text_input->view, uart_text_input_view_input_callback);
+    view_set_enter_callback(uart_text_input->view, text_input_view_enter_callback);
+    view_set_exit_callback(uart_text_input->view, text_input_view_exit_callback);
 
 
-    uart_text_input->timer =
+    uart_text_input->timer = 
         furi_timer_alloc(uart_text_input_timer_callback, FuriTimerTypeOnce, uart_text_input);
         furi_timer_alloc(uart_text_input_timer_callback, FuriTimerTypeOnce, uart_text_input);
 
 
     with_view_model(
     with_view_model(
-        uart_text_input->view,
-        UART_TextInputModel * model,
-        { model->validator_text = furi_string_alloc(); },
+        uart_text_input->view, 
+        UART_TextInputModel * model, 
+        { model->validator_text = furi_string_alloc(); }, 
         false);
         false);
 
 
     uart_text_input_reset(uart_text_input);
     uart_text_input_reset(uart_text_input);
@@ -667,9 +875,9 @@ void uart_text_input_free(UART_TextInput *uart_text_input)
 {
 {
     furi_assert(uart_text_input);
     furi_assert(uart_text_input);
     with_view_model(
     with_view_model(
-        uart_text_input->view,
-        UART_TextInputModel * model,
-        { furi_string_free(model->validator_text); },
+        uart_text_input->view, 
+        UART_TextInputModel * model, 
+        { furi_string_free(model->validator_text); }, 
         false);
         false);
 
 
     // Send stop command
     // Send stop command
@@ -713,11 +921,11 @@ View *uart_text_input_get_view(UART_TextInput *uart_text_input)
 }
 }
 
 
 void uart_text_input_set_result_callback(
 void uart_text_input_set_result_callback(
-    UART_TextInput *uart_text_input,
-    UART_TextInputCallback callback,
-    void *callback_context,
-    char *text_buffer,
-    size_t text_buffer_size,
+    UART_TextInput *uart_text_input, 
+    UART_TextInputCallback callback, 
+    void *callback_context, 
+    char *text_buffer, 
+    size_t text_buffer_size, 
     bool clear_default_text)
     bool clear_default_text)
 {
 {
     with_view_model(
     with_view_model(
@@ -732,7 +940,7 @@ void uart_text_input_set_result_callback(
             if (text_buffer && text_buffer[0] != '\0')
             if (text_buffer && text_buffer[0] != '\0')
             {
             {
                 // Set focus on Save
                 // Set focus on Save
-                model->selected_row = 2;
+                model->selected_row = 3;
                 model->selected_column = 8;
                 model->selected_column = 8;
             }
             }
         },
         },
@@ -740,8 +948,8 @@ void uart_text_input_set_result_callback(
 }
 }
 
 
 void uart_text_input_set_validator(
 void uart_text_input_set_validator(
-    UART_TextInput *uart_text_input,
-    UART_TextInputValidatorCallback callback,
+    UART_TextInput *uart_text_input, 
+    UART_TextInputValidatorCallback callback, 
     void *callback_context)
     void *callback_context)
 {
 {
     with_view_model(
     with_view_model(
@@ -754,14 +962,14 @@ void uart_text_input_set_validator(
         true);
         true);
 }
 }
 
 
-UART_TextInputValidatorCallback
+UART_TextInputValidatorCallback 
 uart_text_input_get_validator_callback(UART_TextInput *uart_text_input)
 uart_text_input_get_validator_callback(UART_TextInput *uart_text_input)
 {
 {
     UART_TextInputValidatorCallback validator_callback = NULL;
     UART_TextInputValidatorCallback validator_callback = NULL;
     with_view_model(
     with_view_model(
-        uart_text_input->view,
-        UART_TextInputModel * model,
-        { validator_callback = model->validator_callback; },
+        uart_text_input->view, 
+        UART_TextInputModel * model, 
+        { validator_callback = model->validator_callback; }, 
         false);
         false);
     return validator_callback;
     return validator_callback;
 }
 }
@@ -770,9 +978,9 @@ void *uart_text_input_get_validator_callback_context(UART_TextInput *uart_text_i
 {
 {
     void *validator_callback_context = NULL;
     void *validator_callback_context = NULL;
     with_view_model(
     with_view_model(
-        uart_text_input->view,
-        UART_TextInputModel * model,
-        { validator_callback_context = model->validator_callback_context; },
+        uart_text_input->view, 
+        UART_TextInputModel * model, 
+        { validator_callback_context = model->validator_callback_context; }, 
         false);
         false);
     return validator_callback_context;
     return validator_callback_context;
 }
 }