Kaynağa Gözat

Merge pull request #17 from jamisonderek/jamisonderek/rpckeyboard

RPC_KEYBOARD
JBlanked 1 yıl önce
ebeveyn
işleme
b2e0bc5221

+ 2 - 2
alloc/flip_social_alloc.c

@@ -176,11 +176,11 @@ FlipSocialApp *flip_social_app_alloc()
     }
 
     // Allocate Submenu(s)
-    if (!easy_flipper_set_submenu(&app->submenu_logged_out, FlipSocialViewLoggedOutSubmenu, "FlipSocial v0.7", flip_social_callback_exit_app, &app->view_dispatcher))
+    if (!easy_flipper_set_submenu(&app->submenu_logged_out, FlipSocialViewLoggedOutSubmenu, "FlipSocial v0.8", flip_social_callback_exit_app, &app->view_dispatcher))
     {
         return NULL;
     }
-    if (!easy_flipper_set_submenu(&app->submenu_logged_in, FlipSocialViewLoggedInSubmenu, "FlipSocial v0.7", flip_social_callback_exit_app, &app->view_dispatcher))
+    if (!easy_flipper_set_submenu(&app->submenu_logged_in, FlipSocialViewLoggedInSubmenu, "FlipSocial v0.8", flip_social_callback_exit_app, &app->view_dispatcher))
     {
         return NULL;
     }

+ 1 - 1
application.fam

@@ -9,6 +9,6 @@ App(
     fap_icon_assets="assets",
     fap_author="jblanked",
     fap_weburl="https://github.com/jblanked/FlipSocial",
-    fap_version="0.7",
+    fap_version="0.8",
     fap_description="Social media platform for the Flipper Zero.",
 )

+ 3 - 0
assets/CHANGELOG.md

@@ -1,3 +1,6 @@
+## 0.8
+- Add support for RPC_KEYBOARD
+
 ## 0.7
 - Improved memory allocation
 - Increased the max explore users from 50 to 100

+ 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);
+}

+ 211 - 3
text_input/uart_text_input.c

@@ -4,6 +4,7 @@
 #include <gui/elements.h>
 #include "flip_social_icons.h"
 #include <furi.h>
+#include "rpc_keyboard.h"
 
 struct UART_TextInput
 {
@@ -25,6 +26,9 @@ typedef struct
     size_t text_buffer_size;
     bool clear_default_text;
 
+    FuriPubSubSubscription *keyboard_subscription;
+    bool invoke_callback;
+
     UART_TextInputCallback callback;
     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 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;
 
     canvas_clear(canvas);
@@ -634,12 +653,199 @@ void uart_text_input_timer_callback(void *context)
     UART_TextInput *uart_text_input = context;
 
     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,
-        { model->valadator_message_visible = false; },
+        {
+            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,
+        {
+            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);
+}
+
+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 = malloc(sizeof(UART_TextInput));
@@ -648,6 +854,8 @@ UART_TextInput *uart_text_input_alloc()
     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_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 =
         furi_timer_alloc(uart_text_input_timer_callback, FuriTimerTypeOnce, uart_text_input);
@@ -732,7 +940,7 @@ void uart_text_input_set_result_callback(
             if (text_buffer && text_buffer[0] != '\0')
             {
                 // Set focus on Save
-                model->selected_row = 2;
+                model->selected_row = 3;
                 model->selected_column = 8;
             }
         },