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

BadUSB: Keyboard Layouts (#2256)

* BadUSB: Keyboard Layouts
* Apply requested changes pt1
* Add layout file check when we loading config

Co-authored-by: Nikolay Minaylov <nm29719@gmail.com>
Co-authored-by: あく <alleteam@gmail.com>
MX 2 лет назад
Родитель
Сommit
b1f581239b
36 измененных файлов с 323 добавлено и 59 удалено
  1. 70 2
      applications/main/bad_usb/bad_usb_app.c
  2. 8 3
      applications/main/bad_usb/bad_usb_app_i.h
  3. 43 8
      applications/main/bad_usb/bad_usb_script.c
  4. 2 0
      applications/main/bad_usb/bad_usb_script.h
  5. 3 0
      applications/main/bad_usb/bad_usb_settings_filename.h
  6. 53 0
      applications/main/bad_usb/scenes/bad_usb_scene_config.c
  7. 2 0
      applications/main/bad_usb/scenes/bad_usb_scene_config.h
  8. 50 0
      applications/main/bad_usb/scenes/bad_usb_scene_config_layout.c
  9. 11 3
      applications/main/bad_usb/scenes/bad_usb_scene_file_select.c
  10. 17 10
      applications/main/bad_usb/scenes/bad_usb_scene_work.c
  11. 60 31
      applications/main/bad_usb/views/bad_usb_view.c
  12. 4 2
      applications/main/bad_usb/views/bad_usb_view.h
  13. BIN
      assets/icons/Archive/keyboard_10px.png
  14. BIN
      assets/resources/badusb/assets/layouts/ba-BA.kl
  15. BIN
      assets/resources/badusb/assets/layouts/cz_CS.kl
  16. BIN
      assets/resources/badusb/assets/layouts/da-DA.kl
  17. BIN
      assets/resources/badusb/assets/layouts/de-CH.kl
  18. BIN
      assets/resources/badusb/assets/layouts/de-DE.kl
  19. BIN
      assets/resources/badusb/assets/layouts/dvorak.kl
  20. BIN
      assets/resources/badusb/assets/layouts/en-UK.kl
  21. BIN
      assets/resources/badusb/assets/layouts/en-US.kl
  22. BIN
      assets/resources/badusb/assets/layouts/es-ES.kl
  23. BIN
      assets/resources/badusb/assets/layouts/fr-BE.kl
  24. BIN
      assets/resources/badusb/assets/layouts/fr-CH.kl
  25. BIN
      assets/resources/badusb/assets/layouts/fr-FR.kl
  26. BIN
      assets/resources/badusb/assets/layouts/hr-HR.kl
  27. BIN
      assets/resources/badusb/assets/layouts/hu-HU.kl
  28. BIN
      assets/resources/badusb/assets/layouts/it-IT.kl
  29. BIN
      assets/resources/badusb/assets/layouts/nb-NO.kl
  30. BIN
      assets/resources/badusb/assets/layouts/nl-NL.kl
  31. BIN
      assets/resources/badusb/assets/layouts/pt-BR.kl
  32. BIN
      assets/resources/badusb/assets/layouts/pt-PT.kl
  33. BIN
      assets/resources/badusb/assets/layouts/si-SI.kl
  34. BIN
      assets/resources/badusb/assets/layouts/sk-SK.kl
  35. BIN
      assets/resources/badusb/assets/layouts/sv-SE.kl
  36. BIN
      assets/resources/badusb/assets/layouts/tr-TR.kl

+ 70 - 2
applications/main/bad_usb/bad_usb_app.c

@@ -1,9 +1,12 @@
 #include "bad_usb_app_i.h"
+#include "bad_usb_settings_filename.h"
 #include <furi.h>
 #include <furi_hal.h>
 #include <storage/storage.h>
 #include <lib/toolbox/path.h>
 
+#define BAD_USB_SETTINGS_PATH BAD_USB_APP_BASE_FOLDER "/" BAD_USB_SETTINGS_FILE_NAME
+
 static bool bad_usb_app_custom_event_callback(void* context, uint32_t event) {
     furi_assert(context);
     BadUsbApp* app = context;
@@ -22,15 +25,62 @@ static void bad_usb_app_tick_event_callback(void* context) {
     scene_manager_handle_tick_event(app->scene_manager);
 }
 
+static void bad_usb_load_settings(BadUsbApp* app) {
+    File* settings_file = storage_file_alloc(furi_record_open(RECORD_STORAGE));
+    if(storage_file_open(settings_file, BAD_USB_SETTINGS_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) {
+        char chr;
+        while((storage_file_read(settings_file, &chr, 1) == 1) &&
+              !storage_file_eof(settings_file) && !isspace(chr)) {
+            furi_string_push_back(app->keyboard_layout, chr);
+        }
+    } else {
+        furi_string_reset(app->keyboard_layout);
+    }
+    storage_file_close(settings_file);
+    storage_file_free(settings_file);
+
+    if(!furi_string_empty(app->keyboard_layout)) {
+        Storage* fs_api = furi_record_open(RECORD_STORAGE);
+        FileInfo layout_file_info;
+        FS_Error file_check_err = storage_common_stat(
+            fs_api, furi_string_get_cstr(app->keyboard_layout), &layout_file_info);
+        furi_record_close(RECORD_STORAGE);
+        if(file_check_err != FSE_OK) {
+            furi_string_reset(app->keyboard_layout);
+            return;
+        }
+        if(layout_file_info.size != 256) {
+            furi_string_reset(app->keyboard_layout);
+        }
+    }
+}
+
+static void bad_usb_save_settings(BadUsbApp* app) {
+    File* settings_file = storage_file_alloc(furi_record_open(RECORD_STORAGE));
+    if(storage_file_open(settings_file, BAD_USB_SETTINGS_PATH, FSAM_WRITE, FSOM_OPEN_ALWAYS)) {
+        storage_file_write(
+            settings_file,
+            furi_string_get_cstr(app->keyboard_layout),
+            furi_string_size(app->keyboard_layout));
+        storage_file_write(settings_file, "\n", 1);
+    }
+    storage_file_close(settings_file);
+    storage_file_free(settings_file);
+}
+
 BadUsbApp* bad_usb_app_alloc(char* arg) {
     BadUsbApp* app = malloc(sizeof(BadUsbApp));
 
-    app->file_path = furi_string_alloc();
+    app->bad_usb_script = NULL;
 
+    app->file_path = furi_string_alloc();
+    app->keyboard_layout = furi_string_alloc();
     if(arg && strlen(arg)) {
         furi_string_set(app->file_path, arg);
     }
 
+    bad_usb_load_settings(app);
+
     app->gui = furi_record_open(RECORD_GUI);
     app->notifications = furi_record_open(RECORD_NOTIFICATION);
     app->dialogs = furi_record_open(RECORD_DIALOGS);
@@ -53,6 +103,10 @@ BadUsbApp* bad_usb_app_alloc(char* arg) {
     view_dispatcher_add_view(
         app->view_dispatcher, BadUsbAppViewError, widget_get_view(app->widget));
 
+    app->submenu = submenu_alloc();
+    view_dispatcher_add_view(
+        app->view_dispatcher, BadUsbAppViewConfig, submenu_get_view(app->submenu));
+
     app->bad_usb_view = bad_usb_alloc();
     view_dispatcher_add_view(
         app->view_dispatcher, BadUsbAppViewWork, bad_usb_get_view(app->bad_usb_view));
@@ -64,9 +118,11 @@ BadUsbApp* bad_usb_app_alloc(char* arg) {
         scene_manager_next_scene(app->scene_manager, BadUsbSceneError);
     } else {
         if(!furi_string_empty(app->file_path)) {
+            app->bad_usb_script = bad_usb_script_open(app->file_path);
+            bad_usb_script_set_keyboard_layout(app->bad_usb_script, app->keyboard_layout);
             scene_manager_next_scene(app->scene_manager, BadUsbSceneWork);
         } else {
-            furi_string_set(app->file_path, BAD_USB_APP_PATH_FOLDER);
+            furi_string_set(app->file_path, BAD_USB_APP_BASE_FOLDER);
             scene_manager_next_scene(app->scene_manager, BadUsbSceneFileSelect);
         }
     }
@@ -77,6 +133,11 @@ BadUsbApp* bad_usb_app_alloc(char* arg) {
 void bad_usb_app_free(BadUsbApp* app) {
     furi_assert(app);
 
+    if(app->bad_usb_script) {
+        bad_usb_script_close(app->bad_usb_script);
+        app->bad_usb_script = NULL;
+    }
+
     // Views
     view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewWork);
     bad_usb_free(app->bad_usb_view);
@@ -85,6 +146,10 @@ void bad_usb_app_free(BadUsbApp* app) {
     view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewError);
     widget_free(app->widget);
 
+    // Submenu
+    view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewConfig);
+    submenu_free(app->submenu);
+
     // View dispatcher
     view_dispatcher_free(app->view_dispatcher);
     scene_manager_free(app->scene_manager);
@@ -94,7 +159,10 @@ void bad_usb_app_free(BadUsbApp* app) {
     furi_record_close(RECORD_NOTIFICATION);
     furi_record_close(RECORD_DIALOGS);
 
+    bad_usb_save_settings(app);
+
     furi_string_free(app->file_path);
+    furi_string_free(app->keyboard_layout);
 
     free(app);
 }

+ 8 - 3
applications/main/bad_usb/bad_usb_app_i.h

@@ -15,8 +15,10 @@
 #include <gui/modules/widget.h>
 #include "views/bad_usb_view.h"
 
-#define BAD_USB_APP_PATH_FOLDER ANY_PATH("badusb")
-#define BAD_USB_APP_EXTENSION ".txt"
+#define BAD_USB_APP_BASE_FOLDER ANY_PATH("badusb")
+#define BAD_USB_APP_PATH_LAYOUT_FOLDER BAD_USB_APP_BASE_FOLDER "/assets/layouts"
+#define BAD_USB_APP_SCRIPT_EXTENSION ".txt"
+#define BAD_USB_APP_LAYOUT_EXTENSION ".kl"
 
 typedef enum {
     BadUsbAppErrorNoFiles,
@@ -30,9 +32,11 @@ struct BadUsbApp {
     NotificationApp* notifications;
     DialogsApp* dialogs;
     Widget* widget;
+    Submenu* submenu;
 
     BadUsbAppError error;
     FuriString* file_path;
+    FuriString* keyboard_layout;
     BadUsb* bad_usb_view;
     BadUsbScript* bad_usb_script;
 };
@@ -40,4 +44,5 @@ struct BadUsbApp {
 typedef enum {
     BadUsbAppViewError,
     BadUsbAppViewWork,
-} BadUsbAppView;
+    BadUsbAppViewConfig,
+} BadUsbAppView;

+ 43 - 8
applications/main/bad_usb/bad_usb_script.c

@@ -16,6 +16,9 @@
 #define SCRIPT_STATE_END (-2)
 #define SCRIPT_STATE_NEXT_LINE (-3)
 
+#define BADUSB_ASCII_TO_KEY(script, x) \
+    (((uint8_t)x < 128) ? (script->layout[(uint8_t)x]) : HID_KEYBOARD_NONE)
+
 typedef enum {
     WorkerEvtToggle = (1 << 0),
     WorkerEvtEnd = (1 << 1),
@@ -28,6 +31,7 @@ struct BadUsbScript {
     BadUsbState st;
     FuriString* file_path;
     uint32_t defdelay;
+    uint16_t layout[128];
     FuriThread* thread;
     uint8_t file_buf[FILE_BUFFER_LEN + 1];
     uint8_t buf_start;
@@ -205,10 +209,10 @@ static bool ducky_altstring(const char* param) {
     return state;
 }
 
-static bool ducky_string(const char* param) {
+static bool ducky_string(BadUsbScript* bad_usb, const char* param) {
     uint32_t i = 0;
     while(param[i] != '\0') {
-        uint16_t keycode = HID_ASCII_TO_KEY(param[i]);
+        uint16_t keycode = BADUSB_ASCII_TO_KEY(bad_usb, param[i]);
         if(keycode != HID_KEYBOARD_NONE) {
             furi_hal_hid_kb_press(keycode);
             furi_hal_hid_kb_release(keycode);
@@ -218,7 +222,7 @@ static bool ducky_string(const char* param) {
     return true;
 }
 
-static uint16_t ducky_get_keycode(const char* param, bool accept_chars) {
+static uint16_t ducky_get_keycode(BadUsbScript* bad_usb, const char* param, bool accept_chars) {
     for(size_t i = 0; i < (sizeof(ducky_keys) / sizeof(ducky_keys[0])); i++) {
         size_t key_cmd_len = strlen(ducky_keys[i].name);
         if((strncmp(param, ducky_keys[i].name, key_cmd_len) == 0) &&
@@ -227,7 +231,7 @@ static uint16_t ducky_get_keycode(const char* param, bool accept_chars) {
         }
     }
     if((accept_chars) && (strlen(param) > 0)) {
-        return (HID_ASCII_TO_KEY(param[0]) & 0xFF);
+        return (BADUSB_ASCII_TO_KEY(bad_usb, param[0]) & 0xFF);
     }
     return 0;
 }
@@ -276,7 +280,7 @@ static int32_t
     } else if(strncmp(line_tmp, ducky_cmd_string, strlen(ducky_cmd_string)) == 0) {
         // STRING
         line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
-        state = ducky_string(line_tmp);
+        state = ducky_string(bad_usb, line_tmp);
         if(!state && error != NULL) {
             snprintf(error, error_len, "Invalid string %s", line_tmp);
         }
@@ -312,14 +316,14 @@ static int32_t
     } else if(strncmp(line_tmp, ducky_cmd_sysrq, strlen(ducky_cmd_sysrq)) == 0) {
         // SYSRQ
         line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
-        uint16_t key = ducky_get_keycode(line_tmp, true);
+        uint16_t key = ducky_get_keycode(bad_usb, line_tmp, true);
         furi_hal_hid_kb_press(KEY_MOD_LEFT_ALT | HID_KEYBOARD_PRINT_SCREEN);
         furi_hal_hid_kb_press(key);
         furi_hal_hid_kb_release_all();
         return (0);
     } else {
         // Special keys + modifiers
-        uint16_t key = ducky_get_keycode(line_tmp, false);
+        uint16_t key = ducky_get_keycode(bad_usb, line_tmp, false);
         if(key == HID_KEYBOARD_NONE) {
             if(error != NULL) {
                 snprintf(error, error_len, "No keycode defined for %s", line_tmp);
@@ -329,7 +333,7 @@ static int32_t
         if((key & 0xFF00) != 0) {
             // It's a modifier key
             line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
-            key |= ducky_get_keycode(line_tmp, true);
+            key |= ducky_get_keycode(bad_usb, line_tmp, true);
         }
         furi_hal_hid_kb_press(key);
         furi_hal_hid_kb_release(key);
@@ -650,12 +654,19 @@ static int32_t bad_usb_worker(void* context) {
     return 0;
 }
 
+static void bad_usb_script_set_default_keyboard_layout(BadUsbScript* bad_usb) {
+    furi_assert(bad_usb);
+    memset(bad_usb->layout, HID_KEYBOARD_NONE, sizeof(bad_usb->layout));
+    memcpy(bad_usb->layout, hid_asciimap, MIN(sizeof(hid_asciimap), sizeof(bad_usb->layout)));
+}
+
 BadUsbScript* bad_usb_script_open(FuriString* file_path) {
     furi_assert(file_path);
 
     BadUsbScript* bad_usb = malloc(sizeof(BadUsbScript));
     bad_usb->file_path = furi_string_alloc();
     furi_string_set(bad_usb->file_path, file_path);
+    bad_usb_script_set_default_keyboard_layout(bad_usb);
 
     bad_usb->st.state = BadUsbStateInit;
     bad_usb->st.error[0] = '\0';
@@ -674,6 +685,30 @@ void bad_usb_script_close(BadUsbScript* bad_usb) {
     free(bad_usb);
 }
 
+void bad_usb_script_set_keyboard_layout(BadUsbScript* bad_usb, FuriString* layout_path) {
+    furi_assert(bad_usb);
+
+    if((bad_usb->st.state == BadUsbStateRunning) || (bad_usb->st.state == BadUsbStateDelay)) {
+        // do not update keyboard layout while a script is running
+        return;
+    }
+
+    File* layout_file = storage_file_alloc(furi_record_open(RECORD_STORAGE));
+    if(!furi_string_empty(layout_path)) {
+        if(storage_file_open(
+               layout_file, furi_string_get_cstr(layout_path), FSAM_READ, FSOM_OPEN_EXISTING)) {
+            uint16_t layout[128];
+            if(storage_file_read(layout_file, layout, sizeof(layout)) == sizeof(layout)) {
+                memcpy(bad_usb->layout, layout, sizeof(layout));
+            }
+        }
+        storage_file_close(layout_file);
+    } else {
+        bad_usb_script_set_default_keyboard_layout(bad_usb);
+    }
+    storage_file_free(layout_file);
+}
+
 void bad_usb_script_toggle(BadUsbScript* bad_usb) {
     furi_assert(bad_usb);
     furi_thread_flags_set(furi_thread_get_id(bad_usb->thread), WorkerEvtToggle);

+ 2 - 0
applications/main/bad_usb/bad_usb_script.h

@@ -33,6 +33,8 @@ BadUsbScript* bad_usb_script_open(FuriString* file_path);
 
 void bad_usb_script_close(BadUsbScript* bad_usb);
 
+void bad_usb_script_set_keyboard_layout(BadUsbScript* bad_usb, FuriString* layout_path);
+
 void bad_usb_script_start(BadUsbScript* bad_usb);
 
 void bad_usb_script_stop(BadUsbScript* bad_usb);

+ 3 - 0
applications/main/bad_usb/bad_usb_settings_filename.h

@@ -0,0 +1,3 @@
+#pragma once
+
+#define BAD_USB_SETTINGS_FILE_NAME ".badusb.settings"

+ 53 - 0
applications/main/bad_usb/scenes/bad_usb_scene_config.c

@@ -0,0 +1,53 @@
+#include "../bad_usb_app_i.h"
+#include "furi_hal_power.h"
+#include "furi_hal_usb.h"
+
+enum SubmenuIndex {
+    SubmenuIndexKeyboardLayout,
+};
+
+void bad_usb_scene_config_submenu_callback(void* context, uint32_t index) {
+    BadUsbApp* bad_usb = context;
+    view_dispatcher_send_custom_event(bad_usb->view_dispatcher, index);
+}
+
+void bad_usb_scene_config_on_enter(void* context) {
+    BadUsbApp* bad_usb = context;
+    Submenu* submenu = bad_usb->submenu;
+
+    submenu_add_item(
+        submenu,
+        "Keyboard layout",
+        SubmenuIndexKeyboardLayout,
+        bad_usb_scene_config_submenu_callback,
+        bad_usb);
+
+    submenu_set_selected_item(
+        submenu, scene_manager_get_scene_state(bad_usb->scene_manager, BadUsbSceneConfig));
+
+    view_dispatcher_switch_to_view(bad_usb->view_dispatcher, BadUsbAppViewConfig);
+}
+
+bool bad_usb_scene_config_on_event(void* context, SceneManagerEvent event) {
+    BadUsbApp* bad_usb = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        scene_manager_set_scene_state(bad_usb->scene_manager, BadUsbSceneConfig, event.event);
+        consumed = true;
+        if(event.event == SubmenuIndexKeyboardLayout) {
+            scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneConfigLayout);
+        } else {
+            furi_crash("Unknown key type");
+        }
+    }
+
+    return consumed;
+}
+
+void bad_usb_scene_config_on_exit(void* context) {
+    BadUsbApp* bad_usb = context;
+    Submenu* submenu = bad_usb->submenu;
+
+    submenu_reset(submenu);
+}

+ 2 - 0
applications/main/bad_usb/scenes/bad_usb_scene_config.h

@@ -1,3 +1,5 @@
 ADD_SCENE(bad_usb, file_select, FileSelect)
 ADD_SCENE(bad_usb, work, Work)
 ADD_SCENE(bad_usb, error, Error)
+ADD_SCENE(bad_usb, config, Config)
+ADD_SCENE(bad_usb, config_layout, ConfigLayout)

+ 50 - 0
applications/main/bad_usb/scenes/bad_usb_scene_config_layout.c

@@ -0,0 +1,50 @@
+#include "../bad_usb_app_i.h"
+#include "furi_hal_power.h"
+#include "furi_hal_usb.h"
+#include <storage/storage.h>
+
+static bool bad_usb_layout_select(BadUsbApp* bad_usb) {
+    furi_assert(bad_usb);
+
+    FuriString* predefined_path;
+    predefined_path = furi_string_alloc();
+    if(!furi_string_empty(bad_usb->keyboard_layout)) {
+        furi_string_set(predefined_path, bad_usb->keyboard_layout);
+    } else {
+        furi_string_set(predefined_path, BAD_USB_APP_PATH_LAYOUT_FOLDER);
+    }
+
+    DialogsFileBrowserOptions browser_options;
+    dialog_file_browser_set_basic_options(
+        &browser_options, BAD_USB_APP_LAYOUT_EXTENSION, &I_keyboard_10px);
+    browser_options.base_path = BAD_USB_APP_PATH_LAYOUT_FOLDER;
+    browser_options.skip_assets = false;
+
+    // Input events and views are managed by file_browser
+    bool res = dialog_file_browser_show(
+        bad_usb->dialogs, bad_usb->keyboard_layout, predefined_path, &browser_options);
+
+    furi_string_free(predefined_path);
+    return res;
+}
+
+void bad_usb_scene_config_layout_on_enter(void* context) {
+    BadUsbApp* bad_usb = context;
+
+    if(bad_usb_layout_select(bad_usb)) {
+        bad_usb_script_set_keyboard_layout(bad_usb->bad_usb_script, bad_usb->keyboard_layout);
+    }
+    scene_manager_previous_scene(bad_usb->scene_manager);
+}
+
+bool bad_usb_scene_config_layout_on_event(void* context, SceneManagerEvent event) {
+    UNUSED(context);
+    UNUSED(event);
+    // BadUsbApp* bad_usb = context;
+    return false;
+}
+
+void bad_usb_scene_config_layout_on_exit(void* context) {
+    UNUSED(context);
+    // BadUsbApp* bad_usb = context;
+}

+ 11 - 3
applications/main/bad_usb/scenes/bad_usb_scene_file_select.c

@@ -7,8 +7,10 @@ static bool bad_usb_file_select(BadUsbApp* bad_usb) {
     furi_assert(bad_usb);
 
     DialogsFileBrowserOptions browser_options;
-    dialog_file_browser_set_basic_options(&browser_options, BAD_USB_APP_EXTENSION, &I_badusb_10px);
-    browser_options.base_path = BAD_USB_APP_PATH_FOLDER;
+    dialog_file_browser_set_basic_options(
+        &browser_options, BAD_USB_APP_SCRIPT_EXTENSION, &I_badusb_10px);
+    browser_options.base_path = BAD_USB_APP_BASE_FOLDER;
+    browser_options.skip_assets = true;
 
     // Input events and views are managed by file_browser
     bool res = dialog_file_browser_show(
@@ -21,12 +23,18 @@ void bad_usb_scene_file_select_on_enter(void* context) {
     BadUsbApp* bad_usb = context;
 
     furi_hal_usb_disable();
+    if(bad_usb->bad_usb_script) {
+        bad_usb_script_close(bad_usb->bad_usb_script);
+        bad_usb->bad_usb_script = NULL;
+    }
 
     if(bad_usb_file_select(bad_usb)) {
+        bad_usb->bad_usb_script = bad_usb_script_open(bad_usb->file_path);
+        bad_usb_script_set_keyboard_layout(bad_usb->bad_usb_script, bad_usb->keyboard_layout);
+
         scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneWork);
     } else {
         furi_hal_usb_enable();
-        //scene_manager_previous_scene(bad_usb->scene_manager);
         view_dispatcher_stop(bad_usb->view_dispatcher);
     }
 }

+ 17 - 10
applications/main/bad_usb/scenes/bad_usb_scene_work.c

@@ -4,10 +4,10 @@
 #include <furi_hal.h>
 #include "toolbox/path.h"
 
-void bad_usb_scene_work_ok_callback(InputType type, void* context) {
+void bad_usb_scene_work_button_callback(InputKey key, void* context) {
     furi_assert(context);
     BadUsbApp* app = context;
-    view_dispatcher_send_custom_event(app->view_dispatcher, type);
+    view_dispatcher_send_custom_event(app->view_dispatcher, key);
 }
 
 bool bad_usb_scene_work_on_event(void* context, SceneManagerEvent event) {
@@ -15,8 +15,13 @@ bool bad_usb_scene_work_on_event(void* context, SceneManagerEvent event) {
     bool consumed = false;
 
     if(event.type == SceneManagerEventTypeCustom) {
-        bad_usb_script_toggle(app->bad_usb_script);
-        consumed = true;
+        if(event.event == InputKeyLeft) {
+            scene_manager_next_scene(app->scene_manager, BadUsbSceneConfig);
+            consumed = true;
+        } else if(event.event == InputKeyOk) {
+            bad_usb_script_toggle(app->bad_usb_script);
+            consumed = true;
+        }
     } else if(event.type == SceneManagerEventTypeTick) {
         bad_usb_set_state(app->bad_usb_view, bad_usb_script_get_state(app->bad_usb_script));
     }
@@ -28,20 +33,22 @@ void bad_usb_scene_work_on_enter(void* context) {
 
     FuriString* file_name;
     file_name = furi_string_alloc();
-
     path_extract_filename(app->file_path, file_name, true);
     bad_usb_set_file_name(app->bad_usb_view, furi_string_get_cstr(file_name));
-    app->bad_usb_script = bad_usb_script_open(app->file_path);
-
     furi_string_free(file_name);
 
+    FuriString* layout;
+    layout = furi_string_alloc();
+    path_extract_filename(app->keyboard_layout, layout, true);
+    bad_usb_set_layout(app->bad_usb_view, furi_string_get_cstr(layout));
+    furi_string_free(layout);
+
     bad_usb_set_state(app->bad_usb_view, bad_usb_script_get_state(app->bad_usb_script));
 
-    bad_usb_set_ok_callback(app->bad_usb_view, bad_usb_scene_work_ok_callback, app);
+    bad_usb_set_button_callback(app->bad_usb_view, bad_usb_scene_work_button_callback, app);
     view_dispatcher_switch_to_view(app->view_dispatcher, BadUsbAppViewWork);
 }
 
 void bad_usb_scene_work_on_exit(void* context) {
-    BadUsbApp* app = context;
-    bad_usb_script_close(app->bad_usb_script);
+    UNUSED(context);
 }

+ 60 - 31
applications/main/bad_usb/views/bad_usb_view.c

@@ -1,5 +1,6 @@
 #include "bad_usb_view.h"
 #include "../bad_usb_script.h"
+#include <toolbox/path.h>
 #include <gui/elements.h>
 #include <assets_icons.h>
 
@@ -7,12 +8,13 @@
 
 struct BadUsb {
     View* view;
-    BadUsbOkCallback callback;
+    BadUsbButtonCallback callback;
     void* context;
 };
 
 typedef struct {
     char file_name[MAX_NAME_LEN];
+    char layout[MAX_NAME_LEN];
     BadUsbState state;
     uint8_t anim_frame;
 } BadUsbModel;
@@ -25,9 +27,23 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) {
     elements_string_fit_width(canvas, disp_str, 128 - 2);
     canvas_set_font(canvas, FontSecondary);
     canvas_draw_str(canvas, 2, 8, furi_string_get_cstr(disp_str));
+
+    if(strlen(model->layout) == 0) {
+        furi_string_set(disp_str, "(default)");
+    } else {
+        furi_string_reset(disp_str);
+        furi_string_push_back(disp_str, '(');
+        for(size_t i = 0; i < strlen(model->layout); i++)
+            furi_string_push_back(disp_str, model->layout[i]);
+        furi_string_push_back(disp_str, ')');
+    }
+    elements_string_fit_width(canvas, disp_str, 128 - 2);
+    canvas_draw_str(
+        canvas, 2, 8 + canvas_current_font_height(canvas), furi_string_get_cstr(disp_str));
+
     furi_string_reset(disp_str);
 
-    canvas_draw_icon(canvas, 22, 20, &I_UsbTree_48x22);
+    canvas_draw_icon(canvas, 22, 24, &I_UsbTree_48x22);
 
     if((model->state.state == BadUsbStateIdle) || (model->state.state == BadUsbStateDone) ||
        (model->state.state == BadUsbStateNotConnected)) {
@@ -38,23 +54,28 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) {
         elements_button_center(canvas, "Cancel");
     }
 
+    if((model->state.state == BadUsbStateNotConnected) ||
+       (model->state.state == BadUsbStateIdle) || (model->state.state == BadUsbStateDone)) {
+        elements_button_left(canvas, "Config");
+    }
+
     if(model->state.state == BadUsbStateNotConnected) {
-        canvas_draw_icon(canvas, 4, 22, &I_Clock_18x18);
+        canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18);
         canvas_set_font(canvas, FontPrimary);
-        canvas_draw_str_aligned(canvas, 127, 27, AlignRight, AlignBottom, "Connect");
-        canvas_draw_str_aligned(canvas, 127, 39, AlignRight, AlignBottom, "to USB");
+        canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Connect");
+        canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "to USB");
     } else if(model->state.state == BadUsbStateWillRun) {
-        canvas_draw_icon(canvas, 4, 22, &I_Clock_18x18);
+        canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18);
         canvas_set_font(canvas, FontPrimary);
-        canvas_draw_str_aligned(canvas, 127, 27, AlignRight, AlignBottom, "Will run");
-        canvas_draw_str_aligned(canvas, 127, 39, AlignRight, AlignBottom, "on connect");
+        canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Will run");
+        canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "on connect");
     } else if(model->state.state == BadUsbStateFileError) {
-        canvas_draw_icon(canvas, 4, 22, &I_Error_18x18);
+        canvas_draw_icon(canvas, 4, 26, &I_Error_18x18);
         canvas_set_font(canvas, FontPrimary);
-        canvas_draw_str_aligned(canvas, 127, 27, AlignRight, AlignBottom, "File");
-        canvas_draw_str_aligned(canvas, 127, 39, AlignRight, AlignBottom, "ERROR");
+        canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "File");
+        canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "ERROR");
     } else if(model->state.state == BadUsbStateScriptError) {
-        canvas_draw_icon(canvas, 4, 22, &I_Error_18x18);
+        canvas_draw_icon(canvas, 4, 26, &I_Error_18x18);
         canvas_set_font(canvas, FontPrimary);
         canvas_draw_str_aligned(canvas, 127, 33, AlignRight, AlignBottom, "ERROR:");
         canvas_set_font(canvas, FontSecondary);
@@ -64,49 +85,49 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) {
         furi_string_reset(disp_str);
         canvas_draw_str_aligned(canvas, 127, 56, AlignRight, AlignBottom, model->state.error);
     } else if(model->state.state == BadUsbStateIdle) {
-        canvas_draw_icon(canvas, 4, 22, &I_Smile_18x18);
+        canvas_draw_icon(canvas, 4, 26, &I_Smile_18x18);
         canvas_set_font(canvas, FontBigNumbers);
-        canvas_draw_str_aligned(canvas, 114, 36, AlignRight, AlignBottom, "0");
-        canvas_draw_icon(canvas, 117, 22, &I_Percent_10x14);
+        canvas_draw_str_aligned(canvas, 114, 40, AlignRight, AlignBottom, "0");
+        canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14);
     } else if(model->state.state == BadUsbStateRunning) {
         if(model->anim_frame == 0) {
-            canvas_draw_icon(canvas, 4, 19, &I_EviSmile1_18x21);
+            canvas_draw_icon(canvas, 4, 23, &I_EviSmile1_18x21);
         } else {
-            canvas_draw_icon(canvas, 4, 19, &I_EviSmile2_18x21);
+            canvas_draw_icon(canvas, 4, 23, &I_EviSmile2_18x21);
         }
         canvas_set_font(canvas, FontBigNumbers);
         furi_string_printf(
             disp_str, "%u", ((model->state.line_cur - 1) * 100) / model->state.line_nb);
         canvas_draw_str_aligned(
-            canvas, 114, 36, AlignRight, AlignBottom, furi_string_get_cstr(disp_str));
+            canvas, 114, 40, AlignRight, AlignBottom, furi_string_get_cstr(disp_str));
         furi_string_reset(disp_str);
-        canvas_draw_icon(canvas, 117, 22, &I_Percent_10x14);
+        canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14);
     } else if(model->state.state == BadUsbStateDone) {
-        canvas_draw_icon(canvas, 4, 19, &I_EviSmile1_18x21);
+        canvas_draw_icon(canvas, 4, 23, &I_EviSmile1_18x21);
         canvas_set_font(canvas, FontBigNumbers);
-        canvas_draw_str_aligned(canvas, 114, 36, AlignRight, AlignBottom, "100");
+        canvas_draw_str_aligned(canvas, 114, 40, AlignRight, AlignBottom, "100");
         furi_string_reset(disp_str);
-        canvas_draw_icon(canvas, 117, 22, &I_Percent_10x14);
+        canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14);
     } else if(model->state.state == BadUsbStateDelay) {
         if(model->anim_frame == 0) {
-            canvas_draw_icon(canvas, 4, 19, &I_EviWaiting1_18x21);
+            canvas_draw_icon(canvas, 4, 23, &I_EviWaiting1_18x21);
         } else {
-            canvas_draw_icon(canvas, 4, 19, &I_EviWaiting2_18x21);
+            canvas_draw_icon(canvas, 4, 23, &I_EviWaiting2_18x21);
         }
         canvas_set_font(canvas, FontBigNumbers);
         furi_string_printf(
             disp_str, "%u", ((model->state.line_cur - 1) * 100) / model->state.line_nb);
         canvas_draw_str_aligned(
-            canvas, 114, 36, AlignRight, AlignBottom, furi_string_get_cstr(disp_str));
+            canvas, 114, 40, AlignRight, AlignBottom, furi_string_get_cstr(disp_str));
         furi_string_reset(disp_str);
-        canvas_draw_icon(canvas, 117, 22, &I_Percent_10x14);
+        canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14);
         canvas_set_font(canvas, FontSecondary);
         furi_string_printf(disp_str, "delay %lus", model->state.delay_remain);
         canvas_draw_str_aligned(
-            canvas, 127, 46, AlignRight, AlignBottom, furi_string_get_cstr(disp_str));
+            canvas, 127, 50, AlignRight, AlignBottom, furi_string_get_cstr(disp_str));
         furi_string_reset(disp_str);
     } else {
-        canvas_draw_icon(canvas, 4, 22, &I_Clock_18x18);
+        canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18);
     }
 
     furi_string_free(disp_str);
@@ -118,10 +139,10 @@ static bool bad_usb_input_callback(InputEvent* event, void* context) {
     bool consumed = false;
 
     if(event->type == InputTypeShort) {
-        if(event->key == InputKeyOk) {
+        if((event->key == InputKeyLeft) || (event->key == InputKeyOk)) {
             consumed = true;
             furi_assert(bad_usb->callback);
-            bad_usb->callback(InputTypeShort, bad_usb->context);
+            bad_usb->callback(event->key, bad_usb->context);
         }
     }
 
@@ -151,7 +172,7 @@ View* bad_usb_get_view(BadUsb* bad_usb) {
     return bad_usb->view;
 }
 
-void bad_usb_set_ok_callback(BadUsb* bad_usb, BadUsbOkCallback callback, void* context) {
+void bad_usb_set_button_callback(BadUsb* bad_usb, BadUsbButtonCallback callback, void* context) {
     furi_assert(bad_usb);
     furi_assert(callback);
     with_view_model(
@@ -174,6 +195,14 @@ void bad_usb_set_file_name(BadUsb* bad_usb, const char* name) {
         true);
 }
 
+void bad_usb_set_layout(BadUsb* bad_usb, const char* layout) {
+    furi_assert(layout);
+    with_view_model(
+        bad_usb->view,
+        BadUsbModel * model,
+        { strlcpy(model->layout, layout, MAX_NAME_LEN); },
+        true);
+}
 void bad_usb_set_state(BadUsb* bad_usb, BadUsbState* st) {
     furi_assert(st);
     with_view_model(

+ 4 - 2
applications/main/bad_usb/views/bad_usb_view.h

@@ -4,7 +4,7 @@
 #include "../bad_usb_script.h"
 
 typedef struct BadUsb BadUsb;
-typedef void (*BadUsbOkCallback)(InputType type, void* context);
+typedef void (*BadUsbButtonCallback)(InputKey key, void* context);
 
 BadUsb* bad_usb_alloc();
 
@@ -12,8 +12,10 @@ void bad_usb_free(BadUsb* bad_usb);
 
 View* bad_usb_get_view(BadUsb* bad_usb);
 
-void bad_usb_set_ok_callback(BadUsb* bad_usb, BadUsbOkCallback callback, void* context);
+void bad_usb_set_button_callback(BadUsb* bad_usb, BadUsbButtonCallback callback, void* context);
 
 void bad_usb_set_file_name(BadUsb* bad_usb, const char* name);
 
+void bad_usb_set_layout(BadUsb* bad_usb, const char* layout);
+
 void bad_usb_set_state(BadUsb* bad_usb, BadUsbState* st);

BIN
assets/icons/Archive/keyboard_10px.png


BIN
assets/resources/badusb/assets/layouts/ba-BA.kl


BIN
assets/resources/badusb/assets/layouts/cz_CS.kl


BIN
assets/resources/badusb/assets/layouts/da-DA.kl


BIN
assets/resources/badusb/assets/layouts/de-CH.kl


BIN
assets/resources/badusb/assets/layouts/de-DE.kl


BIN
assets/resources/badusb/assets/layouts/dvorak.kl


BIN
assets/resources/badusb/assets/layouts/en-UK.kl


BIN
assets/resources/badusb/assets/layouts/en-US.kl


BIN
assets/resources/badusb/assets/layouts/es-ES.kl


BIN
assets/resources/badusb/assets/layouts/fr-BE.kl


BIN
assets/resources/badusb/assets/layouts/fr-CH.kl


BIN
assets/resources/badusb/assets/layouts/fr-FR.kl


BIN
assets/resources/badusb/assets/layouts/hr-HR.kl


BIN
assets/resources/badusb/assets/layouts/hu-HU.kl


BIN
assets/resources/badusb/assets/layouts/it-IT.kl


BIN
assets/resources/badusb/assets/layouts/nb-NO.kl


BIN
assets/resources/badusb/assets/layouts/nl-NL.kl


BIN
assets/resources/badusb/assets/layouts/pt-BR.kl


BIN
assets/resources/badusb/assets/layouts/pt-PT.kl


BIN
assets/resources/badusb/assets/layouts/si-SI.kl


BIN
assets/resources/badusb/assets/layouts/sk-SK.kl


BIN
assets/resources/badusb/assets/layouts/sv-SE.kl


BIN
assets/resources/badusb/assets/layouts/tr-TR.kl