فهرست منبع

add REPL support

Oliver Fabel 1 سال پیش
والد
کامیت
37f7763c61
9فایلهای تغییر یافته به همراه663 افزوده شده و 158 حذف شده
  1. 1 1
      application.fam
  2. 6 1
      docs/pages/quickstart.rst
  3. 1 1
      lib/micropython
  4. 15 3
      lib/micropython-port/mp_flipper_polyfill.c
  5. 24 152
      upython.c
  6. 26 0
      upython.h
  7. 57 0
      upython_file.c
  8. 427 0
      upython_repl.c
  9. 106 0
      upython_splash.c

+ 1 - 1
application.fam

@@ -5,7 +5,7 @@ App(
     entry_point="upython",
     stack_size=4 * 1024,
     fap_category="Tools",
-    fap_version="1.3",
+    fap_version="1.4",
     fap_description="Compile and execute MicroPython scripts",
     fap_icon="icon.png",
     fap_icon_assets="images",

+ 6 - 1
docs/pages/quickstart.rst

@@ -6,11 +6,16 @@ Quickstart
 3. Use the `qFlipper <https://flipperzero.one/update>`_ application to upload the code to your Flipper's SD card.
 4. Start the **uPython** application on your Flipper to execute your Python script.
 
+.. hint::
+
+   Looking for a more efficient solution to copy your files and folders?
+   Try the `Flipper Zero Script SDK <https://github.com/ofabel/fssdk>`_ helper.
+
 Usage
 -----
 
 You can also use the CLI interface to execute a Python scripts:
 
-.. code-block:: Bash
+.. code-block:: bash
 
    loader open /ext/apps/Tools/upython.fap /ext/scripts/tic_tac_toe.py

+ 1 - 1
lib/micropython

@@ -1 +1 @@
-Subproject commit 77538be9d2ff768c630c4cd7a27a8eeaf05d65de
+Subproject commit 17d8f8f7f5c1cabdb2712cdc03da2e262a6d74b8

+ 15 - 3
lib/micropython-port/mp_flipper_polyfill.c

@@ -4,11 +4,23 @@
 #include <math.h>
 #include <string.h>
 
-#define POLYFILL_FUN_1(name, ret, arg1) ret name(arg1)
-#define POLYFILL_FUN_2(name, ret, arg1, arg2) ret name(arg1, arg2)
-#define POLYFILL_FUN_3(name, ret, arg1, arg2, arg3) ret name(arg1, arg2, arg3)
+#define POLYFILL_FUN_1(name, ret, arg1)                   ret name(arg1)
+#define POLYFILL_FUN_2(name, ret, arg1, arg2)             ret name(arg1, arg2)
+#define POLYFILL_FUN_3(name, ret, arg1, arg2, arg3)       ret name(arg1, arg2, arg3)
 #define POLYFILL_FUN_4(name, ret, arg1, arg2, arg3, arg4) ret name(arg1, arg2, arg3, arg4)
 
+#ifndef __aeabi_l2f
+POLYFILL_FUN_1(__aeabi_l2f, float, long long x) {
+    return x;
+}
+#endif
+
+#ifndef __aeabi_f2lz
+POLYFILL_FUN_1(__aeabi_f2lz, long long, float x) {
+    return x;
+}
+#endif
+
 #ifndef __aeabi_dcmple
 POLYFILL_FUN_2(__aeabi_dcmple, double, double, double) {
 }

+ 24 - 152
upython.c

@@ -4,178 +4,50 @@
 #include <gui/gui.h>
 #include <dialogs/dialogs.h>
 #include <storage/storage.h>
+#include <cli/cli.h>
+#include <cli/cli_vcp.h>
 
 #include <mp_flipper_runtime.h>
 #include <mp_flipper_compiler.h>
 
-#include "upython_icons.h"
+#include "upython.h"
 
-#define TAG "uPython"
-
-typedef enum {
-    ActionNone,
-    ActionOpen,
-    ActionExit
-} Action;
-
-static Action action = ActionNone;
-
-static void execute_file(FuriString* file) {
-    size_t stack;
-
-    const char* path = furi_string_get_cstr(file);
-    FuriString* file_path = furi_string_alloc_printf("%s", path);
-
-    do {
-        FURI_LOG_I(TAG, "executing script %s", path);
-
-        const size_t heap_size = memmgr_get_free_heap() * 0.1;
-        const size_t stack_size = 2 * 1024;
-        uint8_t* heap = malloc(heap_size * sizeof(uint8_t));
-
-        FURI_LOG_D(TAG, "initial heap size is %zu bytes", heap_size);
-        FURI_LOG_D(TAG, "stack size is %zu bytes", stack_size);
-
-        size_t index = furi_string_search_rchar(file_path, '/');
-
-        furi_check(index != FURI_STRING_FAILURE);
-
-        bool is_py_file = furi_string_end_with_str(file_path, ".py");
-
-        furi_string_left(file_path, index);
-
-        mp_flipper_set_root_module_path(furi_string_get_cstr(file_path));
-
-        mp_flipper_init(heap, heap_size, stack_size, &stack);
-
-        if(is_py_file) {
-            mp_flipper_exec_py_file(path);
-        } else {
-            mp_flipper_exec_mpy_file(path);
-        }
-
-        mp_flipper_deinit();
-
-        free(heap);
-    } while(false);
-
-    furi_string_free(file_path);
-}
-
-static bool select_python_file(FuriString* file_path) {
-    DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
-
-    DialogsFileBrowserOptions browser_options;
-
-    dialog_file_browser_set_basic_options(&browser_options, "py", NULL);
-
-    browser_options.hide_ext = false;
-    browser_options.base_path = STORAGE_APP_DATA_PATH_PREFIX;
-
-    bool result = dialog_file_browser_show(dialogs, file_path, file_path, &browser_options);
-
-    furi_record_close(RECORD_DIALOGS);
-
-    return result;
-}
-
-static void on_input(const void* event, void* ctx) {
-    UNUSED(ctx);
-
-    InputKey key = ((InputEvent*)event)->key;
-    InputType type = ((InputEvent*)event)->type;
-
-    if(type != InputTypeRelease) {
-        return;
-    }
-
-    switch(key) {
-    case InputKeyOk:
+int32_t upython(void* args) {
+    if(args == NULL) {
+        mp_flipper_repl_register();
+    } else {
         action = ActionOpen;
-        break;
-    case InputKeyBack:
-        action = ActionExit;
-        break;
-    default:
-        action = ActionNone;
-        break;
-    }
-}
-
-static void show_splash_screen() {
-    Gui* gui = furi_record_open(RECORD_GUI);
-    FuriPubSub* input_event_queue = furi_record_open(RECORD_INPUT_EVENTS);
-    FuriPubSubSubscription* input_event = furi_pubsub_subscribe(input_event_queue, on_input, NULL);
-
-    ViewPort* view_port = view_port_alloc();
-
-    gui_add_view_port(gui, view_port, GuiLayerFullscreen);
-
-    Canvas* canvas = gui_direct_draw_acquire(gui);
-
-    canvas_draw_icon(canvas, 0, 0, &I_splash);
-    canvas_draw_icon(canvas, 82, 17, &I_qrcode);
-    canvas_set_color(canvas, ColorBlack);
-    canvas_set_font(canvas, FontSecondary);
-    canvas_draw_str_aligned(canvas, 66, 3, AlignLeft, AlignTop, "Micro");
-    canvas_set_font(canvas, FontPrimary);
-    canvas_draw_str_aligned(canvas, 90, 2, AlignLeft, AlignTop, "Python");
-
-    canvas_set_font(canvas, FontSecondary);
-
-    canvas_draw_icon(canvas, 65, 53, &I_Pin_back_arrow_10x8);
-    canvas_draw_str_aligned(canvas, 78, 54, AlignLeft, AlignTop, "Exit");
-
-    canvas_draw_icon(canvas, 98, 54, &I_ButtonCenter_7x7);
-    canvas_draw_str_aligned(canvas, 107, 54, AlignLeft, AlignTop, "Open");
-
-    canvas_commit(canvas);
-
-    while(action == ActionNone) {
-        furi_delay_ms(1);
     }
 
-    furi_pubsub_unsubscribe(input_event_queue, input_event);
-
-    gui_direct_draw_release(gui);
-    gui_remove_view_port(gui, view_port);
-
-    view_port_free(view_port);
-
-    furi_record_close(RECORD_INPUT_EVENTS);
-    furi_record_close(RECORD_GUI);
-}
-
-int32_t upython(void* args) {
     do {
-        if(args == NULL) {
-            show_splash_screen();
-        }
-
-        if(action == ActionExit) {
+        if(args == NULL && mp_flipper_splash_screen() == ActionExit) {
             break;
         }
 
-        FuriString* file_path = NULL;
+        if(action == ActionOpen) {
+            FuriString* file_path = NULL;
 
-        if(args != NULL) {
-            file_path = furi_string_alloc_set_str(args);
-        } else {
-            file_path = furi_string_alloc_set_str(APP_ASSETS_PATH("upython"));
-        }
+            if(args != NULL) {
+                file_path = furi_string_alloc_set_str(args);
+            } else {
+                file_path = furi_string_alloc_set_str(APP_ASSETS_PATH("upython"));
+            }
 
-        if(args != NULL || select_python_file(file_path)) {
-            execute_file(file_path);
-        }
+            if(args != NULL || mp_flipper_select_python_file(file_path)) {
+                mp_flipper_file_execute(file_path);
+            }
 
-        furi_string_free(file_path);
+            furi_string_free(file_path);
+        }
 
         if(args != NULL) {
             break;
         }
-
-        action = ActionNone;
     } while(true);
 
+    if(args == NULL) {
+        mp_flipper_repl_unregister();
+    }
+
     return 0;
 }

+ 26 - 0
upython.h

@@ -0,0 +1,26 @@
+#pragma once
+
+#include <cli/cli.h>
+#include <furi.h>
+
+#define TAG "uPython"
+
+typedef enum {
+    ActionNone,
+    ActionOpen,
+    ActionRepl,
+    ActionExec,
+    ActionExit
+} Action;
+
+extern Action action;
+
+Action mp_flipper_splash_screen();
+bool mp_flipper_select_python_file(FuriString* file_path);
+
+void mp_flipper_repl_register();
+void mp_flipper_repl_unregister();
+
+void mp_flipper_repl_execute(Cli* cli, FuriString* args, void* ctx);
+
+void mp_flipper_file_execute(FuriString* file);

+ 57 - 0
upython_file.c

@@ -0,0 +1,57 @@
+#include <cli/cli.h>
+#include <furi.h>
+
+#include <genhdr/mpversion.h>
+#include <mp_flipper_compiler.h>
+
+#include <mp_flipper_repl.h>
+
+#include "upython.h"
+
+void mp_flipper_file_execute(FuriString* file) {
+    if(action == ActionOpen) {
+        action = ActionExec;
+    } else {
+        return;
+    }
+
+    size_t stack;
+
+    const char* path = furi_string_get_cstr(file);
+    FuriString* file_path = furi_string_alloc_printf("%s", path);
+
+    do {
+        FURI_LOG_I(TAG, "executing script %s", path);
+
+        const size_t heap_size = memmgr_get_free_heap() * 0.1;
+        const size_t stack_size = 2 * 1024;
+        uint8_t* heap = malloc(heap_size * sizeof(uint8_t));
+
+        FURI_LOG_D(TAG, "initial heap size is %zu bytes", heap_size);
+        FURI_LOG_D(TAG, "stack size is %zu bytes", stack_size);
+
+        size_t index = furi_string_search_rchar(file_path, '/');
+
+        furi_check(index != FURI_STRING_FAILURE);
+
+        bool is_py_file = furi_string_end_with_str(file_path, ".py");
+
+        furi_string_left(file_path, index);
+
+        mp_flipper_set_root_module_path(furi_string_get_cstr(file_path));
+
+        mp_flipper_init(heap, heap_size, stack_size, &stack);
+
+        if(is_py_file) {
+            mp_flipper_exec_py_file(path);
+        }
+
+        mp_flipper_deinit();
+
+        free(heap);
+    } while(false);
+
+    furi_string_free(file_path);
+
+    action = ActionNone;
+}

+ 427 - 0
upython_repl.c

@@ -0,0 +1,427 @@
+#include <stdio.h>
+
+#include <cli/cli.h>
+#include <furi.h>
+
+#include <genhdr/mpversion.h>
+#include <mp_flipper_compiler.h>
+
+#include <mp_flipper_repl.h>
+
+#include "upython.h"
+
+#define AUTOCOMPLETE_MANY_MATCHES (size_t)(-1)
+#define HISTORY_SIZE              16
+
+typedef struct {
+    FuriString** stack;
+    size_t pointer;
+    size_t size;
+} mp_flipper_repl_history_t;
+
+typedef struct {
+    mp_flipper_repl_history_t* history;
+    FuriString* line;
+    FuriString* code;
+    size_t cursor;
+    bool is_ps2;
+} mp_flipper_repl_context_t;
+
+static mp_flipper_repl_history_t* mp_flipper_repl_history_alloc() {
+    mp_flipper_repl_history_t* history = malloc(sizeof(mp_flipper_repl_history_t));
+
+    history->stack = malloc(HISTORY_SIZE * sizeof(FuriString*));
+    history->pointer = 0;
+    history->size = 1;
+
+    for(size_t i = 0; i < HISTORY_SIZE; i++) {
+        history->stack[i] = furi_string_alloc();
+    }
+
+    return history;
+}
+
+static void mp_flipper_repl_history_free(mp_flipper_repl_history_t* history) {
+    for(size_t i = 0; i < HISTORY_SIZE; i++) {
+        furi_string_free(history->stack[i]);
+    }
+
+    free(history);
+}
+
+static mp_flipper_repl_context_t* mp_flipper_repl_context_alloc() {
+    mp_flipper_repl_context_t* context = malloc(sizeof(mp_flipper_repl_context_t));
+
+    context->history = mp_flipper_repl_history_alloc();
+    context->code = furi_string_alloc();
+    context->line = furi_string_alloc();
+    context->cursor = 0;
+    context->is_ps2 = false;
+
+    return context;
+}
+
+static void mp_flipper_repl_context_free(mp_flipper_repl_context_t* context) {
+    mp_flipper_repl_history_free(context->history);
+
+    furi_string_free(context->code);
+    furi_string_free(context->line);
+
+    free(context);
+}
+
+static void print_full_psx(mp_flipper_repl_context_t* context) {
+    const char* psx = context->is_ps2 ? "... " : ">>> ";
+
+    printf("\33[2K\r%s%s", psx, furi_string_get_cstr(context->line));
+
+    for(size_t i = context->cursor; i < furi_string_size(context->line); i++) {
+        printf("\e[D");
+    }
+
+    fflush(stdout);
+}
+
+inline static void handle_arrow_keys(char character, mp_flipper_repl_context_t* context) {
+    mp_flipper_repl_history_t* history = context->history;
+
+    do {
+        bool update_by_history = false;
+        // up arrow
+        if(character == 'A' && history->pointer == 0) {
+            furi_string_set(history->stack[0], context->line);
+        }
+
+        if(character == 'A' && history->pointer < history->size) {
+            history->pointer += (history->pointer + 1) == history->size ? 0 : 1;
+
+            update_by_history = true;
+        }
+
+        // down arrow
+        if(character == 'B' && history->pointer > 0) {
+            history->pointer--;
+
+            update_by_history = true;
+        }
+
+        if(update_by_history) {
+            furi_string_set(context->line, history->stack[history->pointer]);
+
+            context->cursor = furi_string_size(context->line);
+
+            break;
+        }
+
+        // right arrow
+        if(character == 'C' && context->cursor != furi_string_size(context->line)) {
+            context->cursor++;
+
+            break;
+        }
+
+        // left arrow
+        if(character == 'D' && context->cursor > 0) {
+            context->cursor--;
+
+            break;
+        }
+    } while(false);
+
+    print_full_psx(context);
+}
+
+inline static void handle_backspace(mp_flipper_repl_context_t* context) {
+    // skip backspace at begin of line
+    if(context->cursor == 0) {
+        return;
+    }
+
+    const char* line = furi_string_get_cstr(context->line);
+    size_t before = context->cursor - 1;
+    size_t after = furi_string_size(context->line) - context->cursor;
+
+    furi_string_printf(context->line, "%.*s%.*s", before, line, after, line + context->cursor);
+
+    context->cursor--;
+
+    printf("\e[D\e[1P");
+
+    fflush(stdout);
+}
+
+inline static bool is_indent_required(mp_flipper_repl_context_t* context) {
+    for(size_t i = 0; context->is_ps2 && i < context->cursor; i++) {
+        if(furi_string_get_char(context->line, i) != ' ') {
+            return false;
+        }
+    }
+
+    return context->is_ps2;
+}
+
+inline static void handle_autocomplete(mp_flipper_repl_context_t* context) {
+    // check if ps2 is active and just a tab character is required
+    if(is_indent_required(context)) {
+        furi_string_replace_at(context->line, context->cursor, 0, "    ");
+        context->cursor += 4;
+
+        print_full_psx(context);
+
+        return;
+    }
+
+    const char* new_line = furi_string_get_cstr(context->line);
+    FuriString* orig_line = furi_string_alloc_printf("%s", new_line);
+    const char* orig_line_str = furi_string_get_cstr(orig_line);
+
+    char* completion = malloc(128 * sizeof(char));
+
+    mp_print_t* print = malloc(sizeof(mp_print_t));
+
+    print->data = mp_flipper_print_data_alloc();
+    print->print_strn = mp_flipper_print_strn;
+
+    size_t length = mp_flipper_repl_autocomplete(new_line, context->cursor, print, &completion);
+
+    do {
+        if(length == 0) {
+            break;
+        }
+
+        if(length != AUTOCOMPLETE_MANY_MATCHES) {
+            furi_string_printf(
+                context->line,
+                "%.*s%.*s%s",
+                context->cursor,
+                orig_line_str,
+                length,
+                completion,
+                orig_line_str + context->cursor);
+
+            context->cursor += length;
+        } else {
+            printf("%s", mp_flipper_print_get_data(print->data));
+        }
+
+        print_full_psx(context);
+    } while(false);
+
+    mp_flipper_print_data_free(print->data);
+    furi_string_free(orig_line);
+    free(completion);
+    free(print);
+}
+
+inline static void update_history(mp_flipper_repl_context_t* context) {
+    mp_flipper_repl_history_t* history = context->history;
+
+    if(!furi_string_empty(context->line) && !furi_string_equal(context->line, history->stack[1])) {
+        history->size += history->size == HISTORY_SIZE ? 0 : 1;
+
+        for(size_t i = history->size - 1; i > 1; i--) {
+            furi_string_set(history->stack[i], history->stack[i - 1]);
+        }
+
+        furi_string_set(history->stack[1], context->line);
+    }
+
+    furi_string_reset(history->stack[0]);
+
+    history->pointer = 0;
+}
+
+inline static bool continue_with_input(mp_flipper_repl_context_t* context) {
+    if(furi_string_empty(context->line)) {
+        return false;
+    }
+
+    if(!mp_flipper_repl_continue_with_input(furi_string_get_cstr(context->code))) {
+        return false;
+    }
+
+    return true;
+}
+
+void mp_flipper_repl_execute(Cli* cli, FuriString* args, void* ctx) {
+    if(action == ActionNone) {
+        action = ActionRepl;
+    } else {
+        printf("%s is busy - cannot start REPL!\n", TAG);
+
+        return;
+    }
+
+    size_t stack;
+
+    UNUSED(args);
+    UNUSED(ctx);
+
+    const size_t heap_size = memmgr_get_free_heap() * 0.1;
+    const size_t stack_size = 2 * 1024;
+    uint8_t* heap = malloc(heap_size * sizeof(uint8_t));
+
+    printf("MicroPython (%s, %s) on Flipper Zero\r\n", MICROPY_GIT_TAG, MICROPY_BUILD_DATE);
+    printf("Quit: Ctrl+D | Heap: %zu bytes | Stack: %zu bytes\r\n", heap_size, stack_size);
+
+    mp_flipper_repl_context_t* context = mp_flipper_repl_context_alloc();
+
+    mp_flipper_set_root_module_path("/ext");
+    mp_flipper_init(heap, heap_size, stack_size, &stack);
+
+    char character = '\0';
+
+    uint8_t* buffer = malloc(sizeof(uint8_t));
+
+    bool exit = false;
+    bool wait_for_lf = false;
+
+    // REPL loop
+    do {
+        furi_string_reset(context->code);
+
+        context->is_ps2 = false;
+
+        // scan line loop
+        do {
+            furi_string_reset(context->line);
+
+            context->cursor = 0;
+
+            print_full_psx(context);
+
+            // scan character loop
+            do {
+                character = cli_getc(cli);
+
+                // Ctrl + C
+                if(character == CliSymbolAsciiETX) {
+                    context->cursor = 0;
+
+                    furi_string_reset(context->line);
+                    furi_string_reset(context->code);
+
+                    printf("\r\nKeyboardInterrupt\r\n");
+
+                    break;
+                }
+
+                // Ctrl + D
+                if(character == CliSymbolAsciiEOT) {
+                    exit = true;
+
+                    break;
+                }
+
+                // wait for line feed character
+                if(character == CliSymbolAsciiCR) {
+                    wait_for_lf = true;
+
+                    continue;
+                }
+
+                // skip if we don't wait for line feed
+                if(!wait_for_lf && character == CliSymbolAsciiLF) {
+                    continue;
+                }
+
+                // handle line feed
+                if(wait_for_lf && character == CliSymbolAsciiLF) {
+                    furi_string_push_back(context->code, '\n');
+                    furi_string_cat(context->code, context->line);
+                    furi_string_trim(context->code);
+
+                    cli_nl(cli);
+
+                    break;
+                }
+
+                // handle arrow keys
+                if(character >= 0x18 && character <= 0x1B) {
+                    character = cli_getc(cli);
+                    character = cli_getc(cli);
+
+                    handle_arrow_keys(character, context);
+
+                    continue;
+                }
+
+                // handle tab, do autocompletion
+                if(character == CliSymbolAsciiTab) {
+                    handle_autocomplete(context);
+
+                    continue;
+                }
+
+                // handle backspace
+                if(character == CliSymbolAsciiBackspace) {
+                    handle_backspace(context);
+
+                    continue;
+                }
+
+                // append at end
+                if(context->cursor == furi_string_size(context->line)) {
+                    buffer[0] = character;
+                    cli_write(cli, (const uint8_t*)buffer, 1);
+
+                    furi_string_push_back(context->line, character);
+
+                    context->cursor++;
+
+                    continue;
+                }
+
+                // insert between
+                if(context->cursor < furi_string_size(context->line)) {
+                    const char temp[2] = {character, 0};
+                    furi_string_replace_at(context->line, context->cursor++, 0, temp);
+
+                    printf("\e[4h%c\e[4l", character);
+                    fflush(stdout);
+
+                    continue;
+                }
+            } while(true);
+
+            // Ctrl + D
+            if(exit) {
+                break;
+            }
+
+            update_history(context);
+        } while((context->is_ps2 = continue_with_input(context)));
+
+        // Ctrl + D
+        if(exit) {
+            break;
+        }
+
+        mp_flipper_exec_str(furi_string_get_cstr(context->code));
+    } while(true);
+
+    mp_flipper_deinit();
+
+    mp_flipper_repl_context_free(context);
+
+    free(heap);
+    free(buffer);
+
+    action = ActionNone;
+}
+
+void mp_flipper_repl_register() {
+    Cli* cli = furi_record_open(RECORD_CLI);
+
+    cli_add_command(cli, "py", CliCommandFlagParallelSafe, mp_flipper_repl_execute, NULL);
+
+    furi_record_close(RECORD_CLI);
+}
+
+void mp_flipper_repl_unregister() {
+    Cli* cli = furi_record_open(RECORD_CLI);
+
+    cli_delete_command(cli, "py");
+
+    furi_record_close(RECORD_CLI);
+}

+ 106 - 0
upython_splash.c

@@ -0,0 +1,106 @@
+#include <malloc.h>
+
+#include <furi.h>
+#include <gui/gui.h>
+#include <dialogs/dialogs.h>
+#include <storage/storage.h>
+#include <cli/cli.h>
+#include <cli/cli_vcp.h>
+
+#include <mp_flipper_runtime.h>
+#include <mp_flipper_compiler.h>
+
+#include "upython.h"
+#include "upython_icons.h"
+
+Action action = ActionNone;
+
+bool mp_flipper_select_python_file(FuriString* file_path) {
+    DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
+
+    DialogsFileBrowserOptions browser_options;
+
+    dialog_file_browser_set_basic_options(&browser_options, "py", NULL);
+
+    browser_options.hide_ext = false;
+    browser_options.base_path = STORAGE_APP_DATA_PATH_PREFIX;
+
+    bool result = dialog_file_browser_show(dialogs, file_path, file_path, &browser_options);
+
+    furi_record_close(RECORD_DIALOGS);
+
+    return result;
+}
+
+static void on_input(const void* event, void* ctx) {
+    UNUSED(ctx);
+
+    InputKey key = ((InputEvent*)event)->key;
+    InputType type = ((InputEvent*)event)->type;
+
+    if(type != InputTypeRelease) {
+        return;
+    }
+
+    switch(key) {
+    case InputKeyOk:
+        action = ActionOpen;
+        break;
+    case InputKeyBack:
+        action = ActionExit;
+        break;
+    default:
+        action = ActionNone;
+        break;
+    }
+}
+
+Action mp_flipper_splash_screen() {
+    if(action != ActionNone) {
+        return action;
+    }
+
+    Gui* gui = furi_record_open(RECORD_GUI);
+    FuriPubSub* input_event_queue = furi_record_open(RECORD_INPUT_EVENTS);
+    FuriPubSubSubscription* input_event = furi_pubsub_subscribe(input_event_queue, on_input, NULL);
+
+    ViewPort* view_port = view_port_alloc();
+
+    gui_add_view_port(gui, view_port, GuiLayerFullscreen);
+
+    Canvas* canvas = gui_direct_draw_acquire(gui);
+
+    canvas_draw_icon(canvas, 0, 0, &I_splash);
+    canvas_draw_icon(canvas, 82, 17, &I_qrcode);
+    canvas_set_color(canvas, ColorBlack);
+    canvas_set_font(canvas, FontSecondary);
+    canvas_draw_str_aligned(canvas, 66, 3, AlignLeft, AlignTop, "Micro");
+    canvas_set_font(canvas, FontPrimary);
+    canvas_draw_str_aligned(canvas, 90, 2, AlignLeft, AlignTop, "Python");
+
+    canvas_set_font(canvas, FontSecondary);
+
+    canvas_draw_icon(canvas, 65, 53, &I_Pin_back_arrow_10x8);
+    canvas_draw_str_aligned(canvas, 78, 54, AlignLeft, AlignTop, "Exit");
+
+    canvas_draw_icon(canvas, 98, 54, &I_ButtonCenter_7x7);
+    canvas_draw_str_aligned(canvas, 107, 54, AlignLeft, AlignTop, "Open");
+
+    canvas_commit(canvas);
+
+    while(action == ActionNone) {
+        furi_delay_ms(1);
+    }
+
+    furi_pubsub_unsubscribe(input_event_queue, input_event);
+
+    gui_direct_draw_release(gui);
+    gui_remove_view_port(gui, view_port);
+
+    view_port_free(view_port);
+
+    furi_record_close(RECORD_INPUT_EVENTS);
+    furi_record_close(RECORD_GUI);
+
+    return action;
+}