diff --git a/applications/external/bad_bt/bad_bt_paths.h b/applications/external/bad_bt/bad_bt_paths.h index 9c6f4fa324a..f7ce194452c 100644 --- a/applications/external/bad_bt/bad_bt_paths.h +++ b/applications/external/bad_bt/bad_bt_paths.h @@ -5,7 +5,7 @@ #define BAD_BT_APP_BASE_FOLDER EXT_PATH("badusb") #define BAD_BT_APP_BASE_CONFIG_FOLDER EXT_PATH("apps_data/badbt") #define BAD_BT_KEYS_PATH BAD_BT_APP_BASE_CONFIG_FOLDER "/badbt.keys" -#define BAD_BT_SETTINGS_PATH BAD_BT_APP_BASE_CONFIG_FOLDER "/badkb.settings" +#define BAD_BT_SETTINGS_PATH BAD_BT_APP_BASE_CONFIG_FOLDER "/badbt.settings" #define BAD_BT_APP_PATH_LAYOUT_FOLDER BAD_BT_APP_BASE_FOLDER "/assets/layouts" #define BAD_BT_APP_PATH_BOUND_KEYS_FOLDER EXT_PATH("apps_data/badbt") diff --git a/applications/external/bad_bt/helpers/ducky_script.c b/applications/external/bad_bt/helpers/ducky_script.c index 358cc97606f..95cdeef5f38 100644 --- a/applications/external/bad_bt/helpers/ducky_script.c +++ b/applications/external/bad_bt/helpers/ducky_script.c @@ -25,11 +25,11 @@ uint8_t BAD_BT_BOUND_MAC[BAD_BT_MAC_LEN] = FURI_HAL_BT_EMPTY_MAC_ADDR; // Delays for waiting between HID key press and key release const uint8_t bt_hid_delays[LevelRssiNum] = { - 60, // LevelRssi122_100 - 55, // LevelRssi99_80 - 50, // LevelRssi79_60 - 47, // LevelRssi59_40 - 34, // LevelRssi39_0 + 45, // LevelRssi122_100 + 38, // LevelRssi99_80 + 30, // LevelRssi79_60 + 26, // LevelRssi59_40 + 21, // LevelRssi39_0 }; uint8_t bt_timeout = 0; @@ -284,6 +284,7 @@ static bool ducky_set_bt_id(BadBtScript* bad_bt, const char* line) { return false; } } + furi_hal_bt_reverse_mac_addr(cfg->bt_mac); strlcpy(cfg->bt_name, line + mac_len, BAD_BT_NAME_LEN); FURI_LOG_D(WORKER_TAG, "set bt id: %s", line); diff --git a/applications/external/bad_bt/scenes/bad_bt_scene_file_select.c b/applications/external/bad_bt/scenes/bad_bt_scene_file_select.c index b86dc6d713e..8de129298dd 100644 --- a/applications/external/bad_bt/scenes/bad_bt_scene_file_select.c +++ b/applications/external/bad_bt/scenes/bad_bt_scene_file_select.c @@ -1,5 +1,4 @@ #include "../bad_bt_app.h" -#include #include static bool bad_bt_file_select(BadBtApp* bad_bt) { diff --git a/applications/external/bad_kb/application.fam b/applications/external/bad_kb/application.fam new file mode 100644 index 00000000000..af652cf16cc --- /dev/null +++ b/applications/external/bad_kb/application.fam @@ -0,0 +1,13 @@ +App( + appid="bad_kb", + name="Bad KB", + apptype=FlipperAppType.EXTERNAL, + entry_point="bad_kb_app", + stack_size=2 * 1024, + icon="A_BadKb_14", + order=70, + fap_category="USB", + fap_icon="images/badkb_10px.png", + fap_icon_assets="images", + fap_icon_assets_symbol="bad_kb", +) diff --git a/applications/external/bad_kb/bad_kb_app.c b/applications/external/bad_kb/bad_kb_app.c new file mode 100644 index 00000000000..5f51f029178 --- /dev/null +++ b/applications/external/bad_kb/bad_kb_app.c @@ -0,0 +1,273 @@ +#include "bad_kb_app.h" +#include +#include +#include +#include +#include + +#include +#include + +static bool bad_kb_app_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + BadKbApp* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); +} + +static bool bad_kb_app_back_event_callback(void* context) { + furi_assert(context); + BadKbApp* app = context; + return scene_manager_handle_back_event(app->scene_manager); +} + +static void bad_kb_app_tick_event_callback(void* context) { + furi_assert(context); + BadKbApp* app = context; + scene_manager_handle_tick_event(app->scene_manager); +} + +static void bad_kb_load_settings(BadKbApp* app) { + furi_string_reset(app->keyboard_layout); + BadKbConfig* cfg = &app->config; + strcpy(cfg->bt_name, ""); + memcpy(cfg->bt_mac, BAD_KB_EMPTY_MAC, BAD_KB_MAC_LEN); + strcpy(cfg->usb_cfg.manuf, ""); + strcpy(cfg->usb_cfg.product, ""); + cfg->usb_cfg.vid = 0; + cfg->usb_cfg.pid = 0; + + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* file = flipper_format_file_alloc(storage); + if(flipper_format_file_open_existing(file, BAD_KB_SETTINGS_PATH)) { + FuriString* tmp_str = furi_string_alloc(); + if(!flipper_format_read_string(file, "Keyboard_Layout", app->keyboard_layout)) { + furi_string_reset(app->keyboard_layout); + } + if(!flipper_format_read_bool(file, "Is_BT", &(app->is_bt), 1)) { + app->is_bt = false; + } + if(!flipper_format_read_bool(file, "BT_Remember", &(app->bt_remember), 1)) { + app->bt_remember = false; + } + if(flipper_format_read_string(file, "Bt_Name", tmp_str) && !furi_string_empty(tmp_str)) { + strlcpy(cfg->bt_name, furi_string_get_cstr(tmp_str), BAD_KB_NAME_LEN); + } else { + strcpy(cfg->bt_name, ""); + } + if(!flipper_format_read_hex(file, "Bt_Mac", (uint8_t*)&cfg->bt_mac, BAD_KB_MAC_LEN)) { + memcpy(cfg->bt_mac, BAD_KB_EMPTY_MAC, BAD_KB_MAC_LEN); + } + if(flipper_format_read_string(file, "Usb_Manuf", tmp_str) && !furi_string_empty(tmp_str)) { + strlcpy(cfg->usb_cfg.manuf, furi_string_get_cstr(tmp_str), BAD_KB_USB_LEN); + } else { + strcpy(cfg->usb_cfg.manuf, ""); + } + if(flipper_format_read_string(file, "Usb_Product", tmp_str) && + !furi_string_empty(tmp_str)) { + strlcpy(cfg->usb_cfg.product, furi_string_get_cstr(tmp_str), BAD_KB_USB_LEN); + } else { + strcpy(cfg->usb_cfg.product, ""); + } + if(!flipper_format_read_uint32(file, "Usb_Vid", &cfg->usb_cfg.vid, 1)) { + cfg->usb_cfg.vid = 0; + } + if(!flipper_format_read_uint32(file, "Usb_Pid", &cfg->usb_cfg.pid, 1)) { + cfg->usb_cfg.pid = 0; + } + furi_string_free(tmp_str); + flipper_format_file_close(file); + } + flipper_format_free(file); + + if(!furi_string_empty(app->keyboard_layout)) { + FileInfo layout_file_info; + FS_Error file_check_err = storage_common_stat( + storage, furi_string_get_cstr(app->keyboard_layout), &layout_file_info); + 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); + } + } + + furi_record_close(RECORD_STORAGE); +} + +static void bad_kb_save_settings(BadKbApp* app) { + BadKbConfig* cfg = &app->config; + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* file = flipper_format_file_alloc(storage); + if(flipper_format_file_open_always(file, BAD_KB_SETTINGS_PATH)) { + flipper_format_write_string(file, "Keyboard_Layout", app->keyboard_layout); + flipper_format_write_bool(file, "Is_BT", &(app->is_bt), 1); + flipper_format_write_bool(file, "BT_Remember", &(app->bt_remember), 1); + flipper_format_write_string_cstr(file, "Bt_Name", cfg->bt_name); + flipper_format_write_hex(file, "Bt_Mac", (uint8_t*)&cfg->bt_mac, BAD_KB_MAC_LEN); + flipper_format_write_string_cstr(file, "Usb_Manuf", cfg->usb_cfg.manuf); + flipper_format_write_string_cstr(file, "Usb_Product", cfg->usb_cfg.product); + flipper_format_write_uint32(file, "Usb_Vid", &cfg->usb_cfg.vid, 1); + flipper_format_write_uint32(file, "Usb_Pid", &cfg->usb_cfg.pid, 1); + flipper_format_file_close(file); + } + flipper_format_free(file); + furi_record_close(RECORD_STORAGE); +} + +BadKbApp* bad_kb_app_alloc(char* arg) { + BadKbApp* app = malloc(sizeof(BadKbApp)); + + app->bad_kb_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); + } + + Storage* storage = furi_record_open(RECORD_STORAGE); + storage_simply_mkdir(storage, BAD_KB_APP_BASE_FOLDER); + storage_simply_mkdir(storage, BAD_KB_APP_BASE_CONFIG_FOLDER); + furi_record_close(RECORD_STORAGE); + + bad_kb_load_settings(app); + + app->gui = furi_record_open(RECORD_GUI); + app->notifications = furi_record_open(RECORD_NOTIFICATION); + app->dialogs = furi_record_open(RECORD_DIALOGS); + + app->view_dispatcher = view_dispatcher_alloc(); + view_dispatcher_enable_queue(app->view_dispatcher); + + app->scene_manager = scene_manager_alloc(&bad_kb_scene_handlers, app); + + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + view_dispatcher_set_tick_event_callback( + app->view_dispatcher, bad_kb_app_tick_event_callback, 250); + view_dispatcher_set_custom_event_callback( + app->view_dispatcher, bad_kb_app_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, bad_kb_app_back_event_callback); + + Bt* bt = furi_record_open(RECORD_BT); + app->bt = bt; + app->bt->suppress_pin_screen = true; + bad_kb_config_adjust(&app->config); + + // Save prev config + app->prev_usb_mode = furi_hal_usb_get_config(); + FuriHalBtProfile kbd = FuriHalBtProfileHidKeyboard; + app->prev_bt_mode = furi_hal_bt_get_profile_pairing_method(kbd); + memcpy(app->prev_bt_mac, furi_hal_bt_get_profile_mac_addr(kbd), BAD_KB_MAC_LEN); + strlcpy(app->prev_bt_name, furi_hal_bt_get_profile_adv_name(kbd), BAD_KB_NAME_LEN); + + // Adjust BT remember MAC to be serial MAC +2 + memcpy(BAD_KB_BOUND_MAC, furi_hal_version_get_ble_mac(), BAD_KB_MAC_LEN); + BAD_KB_BOUND_MAC[2] += 2; + + // Custom Widget + app->widget = widget_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, BadKbAppViewWidget, widget_get_view(app->widget)); + + app->var_item_list = variable_item_list_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + BadKbAppViewVarItemList, + variable_item_list_get_view(app->var_item_list)); + + app->bad_kb_view = bad_kb_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, BadKbAppViewWork, bad_kb_get_view(app->bad_kb_view)); + + app->text_input = text_input_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, BadKbAppViewTextInput, text_input_get_view(app->text_input)); + + app->byte_input = byte_input_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, BadKbAppViewByteInput, byte_input_get_view(app->byte_input)); + + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + app->conn_mode = BadKbConnModeNone; + app->conn_init_thread = + furi_thread_alloc_ex("BadKbConnInit", 1024, (FuriThreadCallback)bad_kb_conn_apply, app); + furi_thread_start(app->conn_init_thread); + if(!furi_string_empty(app->file_path)) { + app->bad_kb_script = bad_kb_script_open(app->file_path, app->is_bt ? app->bt : NULL, app); + bad_kb_script_set_keyboard_layout(app->bad_kb_script, app->keyboard_layout); + scene_manager_next_scene(app->scene_manager, BadKbSceneWork); + } else { + furi_string_set(app->file_path, BAD_KB_APP_BASE_FOLDER); + scene_manager_next_scene(app->scene_manager, BadKbSceneFileSelect); + } + + return app; +} + +void bad_kb_app_free(BadKbApp* app) { + furi_assert(app); + + if(app->bad_kb_script) { + bad_kb_script_close(app->bad_kb_script); + app->bad_kb_script = NULL; + } + + // Views + view_dispatcher_remove_view(app->view_dispatcher, BadKbAppViewWork); + bad_kb_free(app->bad_kb_view); + + // Custom Widget + view_dispatcher_remove_view(app->view_dispatcher, BadKbAppViewWidget); + widget_free(app->widget); + + // Variable item list + view_dispatcher_remove_view(app->view_dispatcher, BadKbAppViewVarItemList); + variable_item_list_free(app->var_item_list); + + // Text Input + view_dispatcher_remove_view(app->view_dispatcher, BadKbAppViewTextInput); + text_input_free(app->text_input); + + // Byte Input + view_dispatcher_remove_view(app->view_dispatcher, BadKbAppViewByteInput); + byte_input_free(app->byte_input); + + // View dispatcher + view_dispatcher_free(app->view_dispatcher); + scene_manager_free(app->scene_manager); + + // Restore connection config + app->bt->suppress_pin_screen = false; + if(app->conn_init_thread) { + furi_thread_join(app->conn_init_thread); + furi_thread_free(app->conn_init_thread); + app->conn_init_thread = NULL; + } + bad_kb_conn_reset(app); + if(app->hid_cfg) free(app->hid_cfg); + + // Close records + furi_record_close(RECORD_GUI); + furi_record_close(RECORD_NOTIFICATION); + furi_record_close(RECORD_DIALOGS); + furi_record_close(RECORD_BT); + + bad_kb_save_settings(app); + + furi_string_free(app->file_path); + furi_string_free(app->keyboard_layout); + + free(app); +} + +int32_t bad_kb_app(char* p) { + BadKbApp* bad_kb_app = bad_kb_app_alloc(p); + + view_dispatcher_run(bad_kb_app->view_dispatcher); + + bad_kb_app_free(bad_kb_app); + return 0; +} diff --git a/applications/external/bad_kb/bad_kb_app.h b/applications/external/bad_kb/bad_kb_app.h new file mode 100644 index 00000000000..df98abe58f8 --- /dev/null +++ b/applications/external/bad_kb/bad_kb_app.h @@ -0,0 +1,27 @@ +#pragma once + +#include "scenes/bad_kb_scene.h" +#include "helpers/ducky_script.h" + +#include +#include +#include +#include +#include + +#define BAD_KB_APP_SCRIPT_EXTENSION ".txt" +#define BAD_KB_APP_LAYOUT_EXTENSION ".kl" + +typedef enum BadKbCustomEvent { + BadKbAppCustomEventTextInputDone, + BadKbAppCustomEventByteInputDone, + BadKbCustomEventErrorBack +} BadKbCustomEvent; + +typedef enum { + BadKbAppViewWidget, + BadKbAppViewWork, + BadKbAppViewVarItemList, + BadKbAppViewByteInput, + BadKbAppViewTextInput +} BadKbAppView; diff --git a/applications/external/bad_kb/bad_kb_paths.h b/applications/external/bad_kb/bad_kb_paths.h new file mode 100644 index 00000000000..c9451f9c200 --- /dev/null +++ b/applications/external/bad_kb/bad_kb_paths.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +#define BAD_KB_APP_BASE_FOLDER EXT_PATH("badusb") +#define BAD_KB_APP_BASE_CONFIG_FOLDER EXT_PATH("apps_data/badkb") +#define BAD_KB_KEYS_PATH BAD_KB_APP_BASE_CONFIG_FOLDER "/badkb.keys" +#define BAD_KB_SETTINGS_PATH BAD_KB_APP_BASE_CONFIG_FOLDER "/badkb.settings" +#define BAD_KB_APP_PATH_LAYOUT_FOLDER BAD_KB_APP_BASE_FOLDER "/assets/layouts" + +#define BAD_KB_APP_PATH_BOUND_KEYS_FOLDER EXT_PATH("apps_data/badkb") +#define BAD_KB_APP_PATH_BOUND_KEYS_FILE BAD_KB_APP_PATH_BOUND_KEYS_FOLDER "/badkb.keys" diff --git a/applications/external/bad_kb/helpers/ducky_script.c b/applications/external/bad_kb/helpers/ducky_script.c new file mode 100644 index 00000000000..2b13b793964 --- /dev/null +++ b/applications/external/bad_kb/helpers/ducky_script.c @@ -0,0 +1,1098 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ducky_script.h" +#include "ducky_script_i.h" +#include +#include +#include "../scenes/bad_kb_scene.h" + +const uint8_t BAD_KB_EMPTY_MAC[BAD_KB_MAC_LEN] = FURI_HAL_BT_EMPTY_MAC_ADDR; + +// Adjusts to serial MAC +2 in app init +uint8_t BAD_KB_BOUND_MAC[BAD_KB_MAC_LEN] = FURI_HAL_BT_EMPTY_MAC_ADDR; + +#define TAG "BadKB" +#define WORKER_TAG TAG "Worker" + +#define BADKB_ASCII_TO_KEY(script, x) \ + (((uint8_t)x < 128) ? (script->layout[(uint8_t)x]) : HID_KEYBOARD_NONE) + +// Delays for waiting between HID key press and key release +const uint8_t bt_hid_delays[LevelRssiNum] = { + 45, // LevelRssi122_100 + 38, // LevelRssi99_80 + 30, // LevelRssi79_60 + 26, // LevelRssi59_40 + 21, // LevelRssi39_0 +}; + +uint8_t bt_timeout = 0; + +static LevelRssiRange bt_remote_rssi_range(Bt* bt) { + uint8_t rssi; + + if(!bt_remote_rssi(bt, &rssi)) return LevelRssiError; + + if(rssi <= 39) + return LevelRssi39_0; + else if(rssi <= 59) + return LevelRssi59_40; + else if(rssi <= 79) + return LevelRssi79_60; + else if(rssi <= 99) + return LevelRssi99_80; + else if(rssi <= 122) + return LevelRssi122_100; + + return LevelRssiError; +} + +static inline void update_bt_timeout(Bt* bt) { + LevelRssiRange r = bt_remote_rssi_range(bt); + if(r < LevelRssiNum) { + bt_timeout = bt_hid_delays[r]; + FURI_LOG_D(WORKER_TAG, "BLE Key timeout : %u", bt_timeout); + } +} + +typedef enum { + WorkerEvtStartStop = (1 << 0), + WorkerEvtPauseResume = (1 << 1), + WorkerEvtEnd = (1 << 2), + WorkerEvtConnect = (1 << 3), + WorkerEvtDisconnect = (1 << 4), +} WorkerEvtFlags; + +static const char ducky_cmd_id[] = {"ID"}; +static const char ducky_cmd_bt_id[] = {"BT_ID"}; + +static const uint8_t numpad_keys[10] = { + HID_KEYPAD_0, + HID_KEYPAD_1, + HID_KEYPAD_2, + HID_KEYPAD_3, + HID_KEYPAD_4, + HID_KEYPAD_5, + HID_KEYPAD_6, + HID_KEYPAD_7, + HID_KEYPAD_8, + HID_KEYPAD_9, +}; + +uint32_t ducky_get_command_len(const char* line) { + uint32_t len = strlen(line); + for(uint32_t i = 0; i < len; i++) { + if(line[i] == ' ') return i; + } + return 0; +} + +bool ducky_is_line_end(const char chr) { + return ((chr == ' ') || (chr == '\0') || (chr == '\r') || (chr == '\n')); +} + +uint16_t ducky_get_keycode(BadKbScript* bad_kb, const char* param, bool accept_chars) { + uint16_t keycode = ducky_get_keycode_by_name(param); + if(keycode != HID_KEYBOARD_NONE) { + return keycode; + } + + if((accept_chars) && (strlen(param) > 0)) { + return (BADKB_ASCII_TO_KEY(bad_kb, param[0]) & 0xFF); + } + return 0; +} + +bool ducky_get_number(const char* param, uint32_t* val) { + uint32_t value = 0; + if(sscanf(param, "%lu", &value) == 1) { + *val = value; + return true; + } + return false; +} + +void ducky_numlock_on(BadKbScript* bad_kb) { + if(bad_kb->bt) { + if((furi_hal_bt_hid_get_led_state() & HID_KB_LED_NUM) == 0) { + furi_hal_bt_hid_kb_press(HID_KEYBOARD_LOCK_NUM_LOCK); + furi_delay_ms(bt_timeout); + furi_hal_bt_hid_kb_release(HID_KEYBOARD_LOCK_NUM_LOCK); + } + } else { + if((furi_hal_hid_get_led_state() & HID_KB_LED_NUM) == 0) { + furi_hal_hid_kb_press(HID_KEYBOARD_LOCK_NUM_LOCK); + furi_hal_hid_kb_release(HID_KEYBOARD_LOCK_NUM_LOCK); + } + } +} + +bool ducky_numpad_press(BadKbScript* bad_kb, const char num) { + if((num < '0') || (num > '9')) return false; + + uint16_t key = numpad_keys[num - '0']; + if(bad_kb->bt) { + furi_hal_bt_hid_kb_press(key); + furi_delay_ms(bt_timeout); + furi_hal_bt_hid_kb_release(key); + } else { + furi_hal_hid_kb_press(key); + furi_hal_hid_kb_release(key); + } + + return true; +} + +bool ducky_altchar(BadKbScript* bad_kb, const char* charcode) { + uint8_t i = 0; + bool state = false; + + if(bad_kb->bt) { + furi_hal_bt_hid_kb_press(KEY_MOD_LEFT_ALT); + } else { + furi_hal_hid_kb_press(KEY_MOD_LEFT_ALT); + } + + while(!ducky_is_line_end(charcode[i])) { + state = ducky_numpad_press(bad_kb, charcode[i]); + if(state == false) break; + i++; + } + + if(bad_kb->bt) { + furi_hal_bt_hid_kb_release(KEY_MOD_LEFT_ALT); + } else { + furi_hal_hid_kb_release(KEY_MOD_LEFT_ALT); + } + return state; +} + +bool ducky_altstring(BadKbScript* bad_kb, const char* param) { + uint32_t i = 0; + bool state = false; + + while(param[i] != '\0') { + if((param[i] < ' ') || (param[i] > '~')) { + i++; + continue; // Skip non-printable chars + } + + char temp_str[4]; + snprintf(temp_str, 4, "%u", param[i]); + + state = ducky_altchar(bad_kb, temp_str); + if(state == false) break; + i++; + } + return state; +} + +int32_t ducky_error(BadKbScript* bad_kb, const char* text, ...) { + va_list args; + va_start(args, text); + + vsnprintf(bad_kb->st.error, sizeof(bad_kb->st.error), text, args); + + va_end(args); + return SCRIPT_STATE_ERROR; +} + +bool ducky_string(BadKbScript* bad_kb, const char* param) { + uint32_t i = 0; + + while(param[i] != '\0') { + if(param[i] != '\n') { + uint16_t keycode = BADKB_ASCII_TO_KEY(bad_kb, param[i]); + if(keycode != HID_KEYBOARD_NONE) { + if(bad_kb->bt) { + furi_hal_bt_hid_kb_press(keycode); + furi_delay_ms(bt_timeout); + furi_hal_bt_hid_kb_release(keycode); + } else { + furi_hal_hid_kb_press(keycode); + furi_hal_hid_kb_release(keycode); + } + } + } else { + if(bad_kb->bt) { + furi_hal_bt_hid_kb_press(HID_KEYBOARD_RETURN); + furi_delay_ms(bt_timeout); + furi_hal_bt_hid_kb_release(HID_KEYBOARD_RETURN); + } else { + furi_hal_hid_kb_press(HID_KEYBOARD_RETURN); + furi_hal_hid_kb_release(HID_KEYBOARD_RETURN); + } + } + i++; + } + bad_kb->stringdelay = 0; + return true; +} + +static bool ducky_string_next(BadKbScript* bad_kb) { + if(bad_kb->string_print_pos >= furi_string_size(bad_kb->string_print)) { + return true; + } + + char print_char = furi_string_get_char(bad_kb->string_print, bad_kb->string_print_pos); + + if(print_char != '\n') { + uint16_t keycode = BADKB_ASCII_TO_KEY(bad_kb, print_char); + if(keycode != HID_KEYBOARD_NONE) { + if(bad_kb->bt) { + furi_hal_bt_hid_kb_press(keycode); + furi_delay_ms(bt_timeout); + furi_hal_bt_hid_kb_release(keycode); + } else { + furi_hal_hid_kb_press(keycode); + furi_hal_hid_kb_release(keycode); + } + } + } else { + if(bad_kb->bt) { + furi_hal_bt_hid_kb_press(HID_KEYBOARD_RETURN); + furi_delay_ms(bt_timeout); + furi_hal_bt_hid_kb_release(HID_KEYBOARD_RETURN); + } else { + furi_hal_hid_kb_press(HID_KEYBOARD_RETURN); + furi_hal_hid_kb_release(HID_KEYBOARD_RETURN); + } + } + + bad_kb->string_print_pos++; + + return false; +} + +static int32_t ducky_parse_line(BadKbScript* bad_kb, FuriString* line) { + uint32_t line_len = furi_string_size(line); + const char* line_tmp = furi_string_get_cstr(line); + + if(line_len == 0) { + return SCRIPT_STATE_NEXT_LINE; // Skip empty lines + } + FURI_LOG_D(WORKER_TAG, "line:%s", line_tmp); + + // Ducky Lang Functions + int32_t cmd_result = ducky_execute_cmd(bad_kb, line_tmp); + if(cmd_result != SCRIPT_STATE_CMD_UNKNOWN) { + return cmd_result; + } + + // Special keys + modifiers + uint16_t key = ducky_get_keycode(bad_kb, line_tmp, false); + if(key == HID_KEYBOARD_NONE) { + return ducky_error(bad_kb, "No keycode defined for %s", line_tmp); + } + if((key & 0xFF00) != 0) { + // It's a modifier key + uint32_t offset = ducky_get_command_len(line_tmp) + 1; + // ducky_get_command_len() returns 0 without space, so check for != 1 + if(offset != 1 && line_len > offset) { + // It's also a key combination + key |= ducky_get_keycode(bad_kb, line_tmp + offset, true); + } + } + if(bad_kb->bt) { + furi_hal_bt_hid_kb_press(key); + furi_delay_ms(bt_timeout); + furi_hal_bt_hid_kb_release(key); + } else { + furi_hal_hid_kb_press(key); + furi_hal_hid_kb_release(key); + } + return 0; +} + +static bool ducky_set_usb_id(BadKbScript* bad_kb, const char* line) { + FuriHalUsbHidConfig* cfg = &bad_kb->app->id_config.usb_cfg; + + if(sscanf(line, "%lX:%lX", &cfg->vid, &cfg->pid) == 2) { + cfg->manuf[0] = '\0'; + cfg->product[0] = '\0'; + + uint8_t id_len = ducky_get_command_len(line); + if(!ducky_is_line_end(line[id_len + 1])) { + sscanf(&line[id_len + 1], "%31[^\r\n:]:%31[^\r\n]", cfg->manuf, cfg->product); + } + FURI_LOG_D( + WORKER_TAG, + "set usb id: %04lX:%04lX mfr:%s product:%s", + cfg->vid, + cfg->pid, + cfg->manuf, + cfg->product); + return true; + } + return false; +} + +static bool ducky_set_bt_id(BadKbScript* bad_kb, const char* line) { + BadKbConfig* cfg = &bad_kb->app->id_config; + + size_t line_len = strlen(line); + size_t mac_len = BAD_KB_MAC_LEN * 3; // 2 text chars + separator per byte + if(line_len < mac_len + 1) return false; // MAC + at least 1 char for name + + for(size_t i = 0; i < BAD_KB_MAC_LEN; i++) { + char a = line[i * 3]; + char b = line[i * 3 + 1]; + if((a < 'A' && a > 'F') || (a < '0' && a > '9') || (b < 'A' && b > 'F') || + (b < '0' && b > '9') || !hex_char_to_uint8(a, b, &cfg->bt_mac[i])) { + return false; + } + } + furi_hal_bt_reverse_mac_addr(cfg->bt_mac); + + strlcpy(cfg->bt_name, line + mac_len, BAD_KB_NAME_LEN); + FURI_LOG_D(WORKER_TAG, "set bt id: %s", line); + return true; +} + +static void ducky_script_preload(BadKbScript* bad_kb, File* script_file) { + BadKbApp* app = bad_kb->app; + uint8_t ret = 0; + uint32_t line_len = 0; + + furi_string_reset(bad_kb->line); + + do { + ret = storage_file_read(script_file, bad_kb->file_buf, FILE_BUFFER_LEN); + for(uint16_t i = 0; i < ret; i++) { + if(bad_kb->file_buf[i] == '\n' && line_len > 0) { + bad_kb->st.line_nb++; + line_len = 0; + } else { + if(bad_kb->st.line_nb == 0) { // Save first line + furi_string_push_back(bad_kb->line, bad_kb->file_buf[i]); + } + line_len++; + } + } + if(storage_file_eof(script_file)) { + if(line_len > 0) { + bad_kb->st.line_nb++; + break; + } + } + } while(ret > 0); + + // Looking for ID or BT_ID command at first line + const char* line_tmp = furi_string_get_cstr(bad_kb->line); + app->set_usb_id = false; + app->set_bt_id = false; + app->has_usb_id = strncmp(line_tmp, ducky_cmd_id, strlen(ducky_cmd_id)) == 0; + app->has_bt_id = strncmp(line_tmp, ducky_cmd_bt_id, strlen(ducky_cmd_bt_id)) == 0; + + if(app->has_usb_id) { + app->is_bt = false; + app->set_usb_id = ducky_set_usb_id(bad_kb, &line_tmp[strlen(ducky_cmd_id) + 1]); + } else if(app->has_bt_id) { + app->is_bt = true; + app->set_bt_id = ducky_set_bt_id(bad_kb, &line_tmp[strlen(ducky_cmd_bt_id) + 1]); + } + + storage_file_seek(script_file, 0, true); + furi_string_reset(bad_kb->line); +} + +static int32_t ducky_script_execute_next(BadKbScript* bad_kb, File* script_file) { + int32_t delay_val = 0; + + if(bad_kb->repeat_cnt > 0) { + bad_kb->repeat_cnt--; + delay_val = ducky_parse_line(bad_kb, bad_kb->line_prev); + if(delay_val == SCRIPT_STATE_NEXT_LINE) { // Empty line + return 0; + } else if(delay_val == SCRIPT_STATE_STRING_START) { // Print string with delays + return delay_val; + } else if(delay_val == SCRIPT_STATE_WAIT_FOR_BTN) { // wait for button + return delay_val; + } else if(delay_val < 0) { // Script error + bad_kb->st.error_line = bad_kb->st.line_cur - 1; + FURI_LOG_E(WORKER_TAG, "Unknown command at line %u", bad_kb->st.line_cur - 1U); + return SCRIPT_STATE_ERROR; + } else { + return (delay_val + bad_kb->defdelay); + } + } + + furi_string_set(bad_kb->line_prev, bad_kb->line); + furi_string_reset(bad_kb->line); + + while(1) { + if(bad_kb->buf_len == 0) { + bad_kb->buf_len = storage_file_read(script_file, bad_kb->file_buf, FILE_BUFFER_LEN); + if(storage_file_eof(script_file)) { + if((bad_kb->buf_len < FILE_BUFFER_LEN) && (bad_kb->file_end == false)) { + bad_kb->file_buf[bad_kb->buf_len] = '\n'; + bad_kb->buf_len++; + bad_kb->file_end = true; + } + } + + bad_kb->buf_start = 0; + if(bad_kb->buf_len == 0) return SCRIPT_STATE_END; + } + for(uint8_t i = bad_kb->buf_start; i < (bad_kb->buf_start + bad_kb->buf_len); i++) { + if(bad_kb->file_buf[i] == '\n' && furi_string_size(bad_kb->line) > 0) { + bad_kb->st.line_cur++; + bad_kb->buf_len = bad_kb->buf_len + bad_kb->buf_start - (i + 1); + bad_kb->buf_start = i + 1; + furi_string_trim(bad_kb->line); + delay_val = ducky_parse_line(bad_kb, bad_kb->line); + if(delay_val == SCRIPT_STATE_NEXT_LINE) { // Empty line + return 0; + } else if(delay_val == SCRIPT_STATE_STRING_START) { // Print string with delays + return delay_val; + } else if(delay_val == SCRIPT_STATE_WAIT_FOR_BTN) { // wait for button + return delay_val; + } else if(delay_val < 0) { + bad_kb->st.error_line = bad_kb->st.line_cur; + FURI_LOG_E(WORKER_TAG, "Unknown command at line %u", bad_kb->st.line_cur); + return SCRIPT_STATE_ERROR; + } else { + return (delay_val + bad_kb->defdelay); + } + } else { + furi_string_push_back(bad_kb->line, bad_kb->file_buf[i]); + } + } + bad_kb->buf_len = 0; + if(bad_kb->file_end) return SCRIPT_STATE_END; + } + + return 0; +} + +static void bad_kb_bt_hid_state_callback(BtStatus status, void* context) { + furi_assert(context); + BadKbScript* bad_kb = context; + bool state = (status == BtStatusConnected); + + if(state == true) { + LevelRssiRange r = bt_remote_rssi_range(bad_kb->bt); + if(r != LevelRssiError) { + bt_timeout = bt_hid_delays[r]; + } + furi_thread_flags_set(furi_thread_get_id(bad_kb->thread), WorkerEvtConnect); + } else { + furi_thread_flags_set(furi_thread_get_id(bad_kb->thread), WorkerEvtDisconnect); + } +} + +static void bad_kb_usb_hid_state_callback(bool state, void* context) { + furi_assert(context); + BadKbScript* bad_kb = context; + + if(state == true) { + furi_thread_flags_set(furi_thread_get_id(bad_kb->thread), WorkerEvtConnect); + } else { + furi_thread_flags_set(furi_thread_get_id(bad_kb->thread), WorkerEvtDisconnect); + } +} + +static uint32_t bad_kb_flags_get(uint32_t flags_mask, uint32_t timeout) { + uint32_t flags = furi_thread_flags_get(); + furi_check((flags & FuriFlagError) == 0); + if(flags == 0) { + flags = furi_thread_flags_wait(flags_mask, FuriFlagWaitAny, timeout); + furi_check(((flags & FuriFlagError) == 0) || (flags == (unsigned)FuriFlagErrorTimeout)); + } else { + uint32_t state = furi_thread_flags_clear(flags); + furi_check((state & FuriFlagError) == 0); + } + return flags; +} + +int32_t bad_kb_conn_apply(BadKbApp* app) { + if(app->is_bt) { + // Shorthands so this bs is readable + BadKbConfig* cfg = app->set_bt_id ? &app->id_config : &app->config; + FuriHalBtProfile kbd = FuriHalBtProfileHidKeyboard; + + // Setup new config + bt_timeout = bt_hid_delays[LevelRssi39_0]; + bt_disconnect(app->bt); + furi_delay_ms(200); + bt_keys_storage_set_storage_path(app->bt, BAD_KB_KEYS_PATH); + furi_hal_bt_set_profile_adv_name(kbd, cfg->bt_name); + if(app->bt_remember) { + furi_hal_bt_set_profile_mac_addr(kbd, BAD_KB_BOUND_MAC); + furi_hal_bt_set_profile_pairing_method(kbd, GapPairingPinCodeVerifyYesNo); + } else { + furi_hal_bt_set_profile_mac_addr(kbd, cfg->bt_mac); + furi_hal_bt_set_profile_pairing_method(kbd, GapPairingNone); + } + + // Set profile, restart BT, adjust defaults + furi_check(bt_set_profile(app->bt, BtProfileHidKeyboard)); + + // Advertise even if BT is off in settings + furi_hal_bt_start_advertising(); + + // Toggle key callback after since BT restart resets it + if(app->bt_remember) { + bt_enable_peer_key_update(app->bt); + } else { + bt_disable_peer_key_update(app->bt); + } + + app->conn_mode = BadKbConnModeBt; + + } else { + // Unlock RPC connections + furi_hal_usb_unlock(); + + // Context will apply with set_config only if pointer address is different, so we use a copy + FuriHalUsbHidConfig* hid_cfg = malloc(sizeof(FuriHalUsbHidConfig)); + memcpy( + hid_cfg, + app->set_usb_id ? &app->id_config.usb_cfg : &app->config.usb_cfg, + sizeof(FuriHalUsbHidConfig)); + furi_check(furi_hal_usb_set_config(&usb_hid, hid_cfg)); + if(app->hid_cfg) free(app->hid_cfg); + app->hid_cfg = hid_cfg; + + app->conn_mode = BadKbConnModeUsb; + } + + return 0; +} + +void bad_kb_conn_reset(BadKbApp* app) { + if(app->conn_mode == BadKbConnModeBt) { + // TODO: maybe also restore BT profile? + bt_disconnect(app->bt); + furi_delay_ms(200); + bt_keys_storage_set_default_path(app->bt); + FuriHalBtProfile kbd = FuriHalBtProfileHidKeyboard; + furi_hal_bt_set_profile_mac_addr(kbd, app->prev_bt_mac); + furi_hal_bt_set_profile_adv_name(kbd, app->prev_bt_name); + furi_hal_bt_set_profile_pairing_method(kbd, app->prev_bt_mode); + furi_check(bt_set_profile(app->bt, BtProfileSerial)); + bt_enable_peer_key_update(app->bt); + + } else if(app->conn_mode == BadKbConnModeUsb) { + // TODO: maybe also restore USB context? + furi_check(furi_hal_usb_set_config(app->prev_usb_mode, NULL)); + } + + app->conn_mode = BadKbConnModeNone; +} + +void bad_kb_config_adjust(BadKbConfig* cfg) { + // Avoid empty name + if(strcmp(cfg->bt_name, "") == 0) { + snprintf(cfg->bt_name, BAD_KB_NAME_LEN, "Control %s", furi_hal_version_get_name_ptr()); + } + + // MAC is adjusted by furi_hal_bt, adjust here too so it matches after applying + const uint8_t* normal_mac = furi_hal_version_get_ble_mac(); + uint8_t empty_mac[BAD_KB_MAC_LEN] = FURI_HAL_BT_EMPTY_MAC_ADDR; + uint8_t default_mac[BAD_KB_MAC_LEN] = FURI_HAL_BT_DEFAULT_MAC_ADDR; + if(memcmp(cfg->bt_mac, empty_mac, BAD_KB_MAC_LEN) == 0 || + memcmp(cfg->bt_mac, normal_mac, BAD_KB_MAC_LEN) == 0 || + memcmp(cfg->bt_mac, default_mac, BAD_KB_MAC_LEN) == 0) { + memcpy(cfg->bt_mac, normal_mac, BAD_KB_MAC_LEN); + cfg->bt_mac[2]++; + } + + // Use defaults if vid or pid are unset + if(cfg->usb_cfg.vid == 0) cfg->usb_cfg.vid = HID_VID_DEFAULT; + if(cfg->usb_cfg.pid == 0) cfg->usb_cfg.pid = HID_PID_DEFAULT; +} + +void bad_kb_config_refresh(BadKbApp* app) { + bt_set_status_changed_callback(app->bt, NULL, NULL); + furi_hal_hid_set_state_callback(NULL, NULL); + if(app->bad_kb_script) { + furi_thread_flags_set(furi_thread_get_id(app->bad_kb_script->thread), WorkerEvtDisconnect); + } + if(app->conn_init_thread) { + furi_thread_join(app->conn_init_thread); + } + + bool apply = false; + if(app->is_bt) { + BadKbConfig* cfg = app->set_bt_id ? &app->id_config : &app->config; + bad_kb_config_adjust(cfg); + + if(app->conn_mode != BadKbConnModeBt) { + apply = true; + bad_kb_conn_reset(app); + } else { + apply = apply || strncmp( + cfg->bt_name, + furi_hal_bt_get_profile_adv_name(FuriHalBtProfileHidKeyboard), + BAD_KB_NAME_LEN); + apply = apply || memcmp( + app->bt_remember ? BAD_KB_BOUND_MAC : cfg->bt_mac, + furi_hal_bt_get_profile_mac_addr(FuriHalBtProfileHidKeyboard), + BAD_KB_MAC_LEN); + } + } else { + BadKbConfig* cfg = app->set_usb_id ? &app->id_config : &app->config; + bad_kb_config_adjust(cfg); + + if(app->conn_mode != BadKbConnModeUsb) { + apply = true; + bad_kb_conn_reset(app); + } else { + void* ctx; + if(furi_hal_usb_get_config() == &usb_hid && + (ctx = furi_hal_usb_get_config_context()) != NULL) { + FuriHalUsbHidConfig* cur = ctx; + apply = apply || cfg->usb_cfg.vid != cur->vid; + apply = apply || cfg->usb_cfg.pid != cur->pid; + apply = apply || strncmp(cfg->usb_cfg.manuf, cur->manuf, sizeof(cur->manuf)); + apply = apply || strncmp(cfg->usb_cfg.product, cur->product, sizeof(cur->product)); + } else { + apply = true; + } + } + } + + if(apply) { + bad_kb_conn_apply(app); + } + + if(app->bad_kb_script) { + BadKbScript* script = app->bad_kb_script; + script->st.is_bt = app->is_bt; + script->bt = app->is_bt ? app->bt : NULL; + bool connected; + if(app->is_bt) { + bt_set_status_changed_callback(app->bt, bad_kb_bt_hid_state_callback, script); + connected = furi_hal_bt_is_connected(); + } else { + furi_hal_hid_set_state_callback(bad_kb_usb_hid_state_callback, script); + connected = furi_hal_hid_is_connected(); + } + if(connected) { + furi_thread_flags_set(furi_thread_get_id(script->thread), WorkerEvtConnect); + } + } + + // Reload config page + scene_manager_next_scene(app->scene_manager, BadKbSceneConfig); + scene_manager_previous_scene(app->scene_manager); +} + +static int32_t bad_kb_worker(void* context) { + BadKbScript* bad_kb = context; + + BadKbWorkerState worker_state = BadKbStateInit; + BadKbWorkerState pause_state = BadKbStateRunning; + int32_t delay_val = 0; + + FURI_LOG_I(WORKER_TAG, "Init"); + File* script_file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); + bad_kb->line = furi_string_alloc(); + bad_kb->line_prev = furi_string_alloc(); + bad_kb->string_print = furi_string_alloc(); + + while(1) { + if(worker_state == BadKbStateInit) { // State: initialization + FURI_LOG_D(WORKER_TAG, "init start"); + if(storage_file_open( + script_file, + furi_string_get_cstr(bad_kb->file_path), + FSAM_READ, + FSOM_OPEN_EXISTING)) { + ducky_script_preload(bad_kb, script_file); + if(bad_kb->st.line_nb > 0) { + bad_kb_config_refresh(bad_kb->app); + worker_state = BadKbStateNotConnected; // Refresh will set connected flag + } else { + worker_state = BadKbStateScriptError; // Script preload error + } + } else { + FURI_LOG_E(WORKER_TAG, "File open error"); + worker_state = BadKbStateFileError; // File open error + } + bad_kb->st.state = worker_state; + FURI_LOG_D(WORKER_TAG, "init done"); + + } else if(worker_state == BadKbStateNotConnected) { // State: Not connected + FURI_LOG_D(WORKER_TAG, "not connected wait"); + uint32_t flags = bad_kb_flags_get( + WorkerEvtEnd | WorkerEvtConnect | WorkerEvtDisconnect | WorkerEvtStartStop, + FuriWaitForever); + FURI_LOG_D(WORKER_TAG, "not connected flags: %lu", flags); + + if(flags & WorkerEvtEnd) { + break; + } else if(flags & WorkerEvtConnect) { + worker_state = BadKbStateIdle; // Ready to run + } else if(flags & WorkerEvtStartStop) { + worker_state = BadKbStateWillRun; // Will run when connected + } + bad_kb->st.state = worker_state; + + } else if(worker_state == BadKbStateIdle) { // State: ready to start + FURI_LOG_D(WORKER_TAG, "idle wait"); + uint32_t flags = bad_kb_flags_get( + WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtConnect | WorkerEvtDisconnect, + FuriWaitForever); + FURI_LOG_D(WORKER_TAG, "idle flags: %lu", flags); + + if(flags & WorkerEvtEnd) { + break; + } else if(flags & WorkerEvtStartStop) { // Start executing script + delay_val = 0; + bad_kb->buf_len = 0; + bad_kb->st.line_cur = 0; + bad_kb->defdelay = 0; + bad_kb->stringdelay = 0; + bad_kb->repeat_cnt = 0; + bad_kb->key_hold_nb = 0; + bad_kb->file_end = false; + storage_file_seek(script_file, 0, true); + bad_kb_script_set_keyboard_layout(bad_kb, bad_kb->keyboard_layout); + worker_state = BadKbStateRunning; + } else if(flags & WorkerEvtDisconnect) { + worker_state = BadKbStateNotConnected; // Disconnected + } + bad_kb->st.state = worker_state; + + } else if(worker_state == BadKbStateWillRun) { // State: start on connection + FURI_LOG_D(WORKER_TAG, "will run wait"); + uint32_t flags = bad_kb_flags_get( + WorkerEvtEnd | WorkerEvtConnect | WorkerEvtDisconnect | WorkerEvtStartStop, + FuriWaitForever); + FURI_LOG_D(WORKER_TAG, "will run flags: %lu", flags); + + if(flags & WorkerEvtEnd) { + break; + } else if(flags & WorkerEvtConnect) { // Start executing script + delay_val = 0; + bad_kb->buf_len = 0; + bad_kb->st.line_cur = 0; + bad_kb->defdelay = 0; + bad_kb->stringdelay = 0; + bad_kb->repeat_cnt = 0; + bad_kb->file_end = false; + storage_file_seek(script_file, 0, true); + // extra time for PC to recognize Flipper as keyboard + flags = furi_thread_flags_wait( + WorkerEvtEnd | WorkerEvtDisconnect | WorkerEvtStartStop, + FuriFlagWaitAny | FuriFlagNoClear, + bad_kb->bt ? 3000 : 1500); + if(flags == (unsigned)FuriFlagErrorTimeout) { + // If nothing happened - start script execution + worker_state = BadKbStateRunning; + } else if(flags & WorkerEvtStartStop) { + worker_state = BadKbStateIdle; + furi_thread_flags_clear(WorkerEvtStartStop); + } + if(bad_kb->bt) { + update_bt_timeout(bad_kb->bt); + } + bad_kb_script_set_keyboard_layout(bad_kb, bad_kb->keyboard_layout); + } else if(flags & WorkerEvtStartStop) { // Cancel scheduled execution + worker_state = BadKbStateNotConnected; + } + bad_kb->st.state = worker_state; + + } else if(worker_state == BadKbStateRunning) { // State: running + FURI_LOG_D(WORKER_TAG, "running"); + uint16_t delay_cur = (delay_val > 1000) ? (1000) : (delay_val); + uint32_t flags = furi_thread_flags_wait( + WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtConnect | + WorkerEvtDisconnect, + FuriFlagWaitAny, + delay_cur); + FURI_LOG_D(WORKER_TAG, "running flags: %lu", flags); + + delay_val -= delay_cur; + if(!(flags & FuriFlagError)) { + if(flags & WorkerEvtEnd) { + break; + } else if(flags & WorkerEvtStartStop) { + worker_state = BadKbStateIdle; // Stop executing script + if(bad_kb->bt) { + furi_hal_bt_hid_kb_release_all(); + } else { + furi_hal_hid_kb_release_all(); + } + } else if(flags & WorkerEvtDisconnect) { + worker_state = BadKbStateNotConnected; // Disconnected + if(bad_kb->bt) { + furi_hal_bt_hid_kb_release_all(); + } else { + furi_hal_hid_kb_release_all(); + } + } else if(flags & WorkerEvtPauseResume) { + pause_state = BadKbStateRunning; + worker_state = BadKbStatePaused; // Pause + } + bad_kb->st.state = worker_state; + continue; + } else if( + (flags == (unsigned)FuriFlagErrorTimeout) || + (flags == (unsigned)FuriFlagErrorResource)) { + if(delay_val > 0) { + bad_kb->st.delay_remain--; + continue; + } + bad_kb->st.state = BadKbStateRunning; + delay_val = ducky_script_execute_next(bad_kb, script_file); + if(delay_val == SCRIPT_STATE_ERROR) { // Script error + delay_val = 0; + worker_state = BadKbStateScriptError; + bad_kb->st.state = worker_state; + if(bad_kb->bt) { + furi_hal_bt_hid_kb_release_all(); + } else { + furi_hal_hid_kb_release_all(); + } + } else if(delay_val == SCRIPT_STATE_END) { // End of script + delay_val = 0; + worker_state = BadKbStateIdle; + bad_kb->st.state = BadKbStateDone; + if(bad_kb->bt) { + furi_hal_bt_hid_kb_release_all(); + } else { + furi_hal_hid_kb_release_all(); + } + continue; + } else if(delay_val == SCRIPT_STATE_STRING_START) { // Start printing string with delays + delay_val = bad_kb->defdelay; + bad_kb->string_print_pos = 0; + worker_state = BadKbStateStringDelay; + } else if(delay_val == SCRIPT_STATE_WAIT_FOR_BTN) { // set state to wait for user input + worker_state = BadKbStateWaitForBtn; + bad_kb->st.state = BadKbStateWaitForBtn; // Show long delays + } else if(delay_val > 1000) { + bad_kb->st.state = BadKbStateDelay; // Show long delays + bad_kb->st.delay_remain = delay_val / 1000; + } + } else { + furi_check((flags & FuriFlagError) == 0); + } + } else if(worker_state == BadKbStateWaitForBtn) { // State: Wait for button Press + FURI_LOG_D(WORKER_TAG, "button wait"); + uint32_t flags = bad_kb_flags_get( + WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtConnect | + WorkerEvtDisconnect, + FuriWaitForever); + FURI_LOG_D(WORKER_TAG, "button flags: %lu", flags); + if(!(flags & FuriFlagError)) { + if(flags & WorkerEvtEnd) { + break; + } else if(flags & WorkerEvtStartStop) { + delay_val = 0; + worker_state = BadKbStateRunning; + } else if(flags & WorkerEvtDisconnect) { + worker_state = BadKbStateNotConnected; // Disconnected + if(bad_kb->bt) { + furi_hal_bt_hid_kb_release_all(); + } else { + furi_hal_hid_kb_release_all(); + } + } + bad_kb->st.state = worker_state; + continue; + } + } else if(worker_state == BadKbStatePaused) { // State: Paused + FURI_LOG_D(WORKER_TAG, "paused wait"); + uint32_t flags = bad_kb_flags_get( + WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtConnect | + WorkerEvtDisconnect, + FuriWaitForever); + FURI_LOG_D(WORKER_TAG, "paused flags: %lu", flags); + if(!(flags & FuriFlagError)) { + if(flags & WorkerEvtEnd) { + break; + } else if(flags & WorkerEvtStartStop) { + worker_state = BadKbStateIdle; // Stop executing script + bad_kb->st.state = worker_state; + if(bad_kb->bt) { + furi_hal_bt_hid_kb_release_all(); + } else { + furi_hal_hid_kb_release_all(); + } + } else if(flags & WorkerEvtDisconnect) { + worker_state = BadKbStateNotConnected; // Disconnected + bad_kb->st.state = worker_state; + if(bad_kb->bt) { + furi_hal_bt_hid_kb_release_all(); + } else { + furi_hal_hid_kb_release_all(); + } + } else if(flags & WorkerEvtPauseResume) { + if(pause_state == BadKbStateRunning) { + if(delay_val > 0) { + bad_kb->st.state = BadKbStateDelay; + bad_kb->st.delay_remain = delay_val / 1000; + } else { + bad_kb->st.state = BadKbStateRunning; + delay_val = 0; + } + worker_state = BadKbStateRunning; // Resume + } else if(pause_state == BadKbStateStringDelay) { + bad_kb->st.state = BadKbStateRunning; + worker_state = BadKbStateStringDelay; // Resume + } + } + continue; + } + } else if(worker_state == BadKbStateStringDelay) { // State: print string with delays + FURI_LOG_D(WORKER_TAG, "delay wait"); + uint32_t flags = bad_kb_flags_get( + WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtConnect | + WorkerEvtDisconnect, + bad_kb->stringdelay); + FURI_LOG_D(WORKER_TAG, "delay flags: %lu", flags); + + if(!(flags & FuriFlagError)) { + if(flags & WorkerEvtEnd) { + break; + } else if(flags & WorkerEvtStartStop) { + worker_state = BadKbStateIdle; // Stop executing script + if(bad_kb->bt) { + furi_hal_bt_hid_kb_release_all(); + } else { + furi_hal_hid_kb_release_all(); + } + } else if(flags & WorkerEvtDisconnect) { + worker_state = BadKbStateNotConnected; // Disconnected + if(bad_kb->bt) { + furi_hal_bt_hid_kb_release_all(); + } else { + furi_hal_hid_kb_release_all(); + } + } else if(flags & WorkerEvtPauseResume) { + pause_state = BadKbStateStringDelay; + worker_state = BadKbStatePaused; // Pause + } + bad_kb->st.state = worker_state; + continue; + } else if( + (flags == (unsigned)FuriFlagErrorTimeout) || + (flags == (unsigned)FuriFlagErrorResource)) { + bool string_end = ducky_string_next(bad_kb); + if(string_end) { + bad_kb->stringdelay = 0; + worker_state = BadKbStateRunning; + } + } else { + furi_check((flags & FuriFlagError) == 0); + } + } else if( + (worker_state == BadKbStateFileError) || + (worker_state == BadKbStateScriptError)) { // State: error + FURI_LOG_D(WORKER_TAG, "error wait"); + uint32_t flags = + bad_kb_flags_get(WorkerEvtEnd, FuriWaitForever); // Waiting for exit command + FURI_LOG_D(WORKER_TAG, "error flags: %lu", flags); + + if(flags & WorkerEvtEnd) { + break; + } + } + if(bad_kb->bt) { + update_bt_timeout(bad_kb->bt); + } + } + + bt_set_status_changed_callback(bad_kb->app->bt, NULL, NULL); + furi_hal_hid_set_state_callback(NULL, NULL); + + storage_file_close(script_file); + storage_file_free(script_file); + furi_string_free(bad_kb->line); + furi_string_free(bad_kb->line_prev); + furi_string_free(bad_kb->string_print); + + FURI_LOG_I(WORKER_TAG, "End"); + + return 0; +} + +static void bad_kb_script_set_default_keyboard_layout(BadKbScript* bad_kb) { + furi_assert(bad_kb); + furi_string_set_str(bad_kb->keyboard_layout, ""); + memset(bad_kb->layout, HID_KEYBOARD_NONE, sizeof(bad_kb->layout)); + memcpy(bad_kb->layout, hid_asciimap, MIN(sizeof(hid_asciimap), sizeof(bad_kb->layout))); +} + +BadKbScript* bad_kb_script_open(FuriString* file_path, Bt* bt, BadKbApp* app) { + furi_assert(file_path); + + BadKbScript* bad_kb = malloc(sizeof(BadKbScript)); + bad_kb->app = app; + bad_kb->file_path = furi_string_alloc(); + furi_string_set(bad_kb->file_path, file_path); + bad_kb->keyboard_layout = furi_string_alloc(); + bad_kb_script_set_default_keyboard_layout(bad_kb); + + bad_kb->st.state = BadKbStateInit; + bad_kb->st.error[0] = '\0'; + bad_kb->st.is_bt = !!bt; + + bad_kb->bt = bt; + + bad_kb->thread = furi_thread_alloc_ex("BadKbWorker", 2048, bad_kb_worker, bad_kb); + furi_thread_start(bad_kb->thread); + return bad_kb; +} //-V773 + +void bad_kb_script_close(BadKbScript* bad_kb) { + furi_assert(bad_kb); + furi_record_close(RECORD_STORAGE); + furi_thread_flags_set(furi_thread_get_id(bad_kb->thread), WorkerEvtEnd); + furi_thread_join(bad_kb->thread); + furi_thread_free(bad_kb->thread); + furi_string_free(bad_kb->file_path); + furi_string_free(bad_kb->keyboard_layout); + free(bad_kb); +} + +void bad_kb_script_set_keyboard_layout(BadKbScript* bad_kb, FuriString* layout_path) { + furi_assert(bad_kb); + + if((bad_kb->st.state == BadKbStateRunning) || (bad_kb->st.state == BadKbStateDelay)) { + // 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)) { //-V1051 + furi_string_set(bad_kb->keyboard_layout, 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_kb->layout, layout, sizeof(layout)); + } + } + storage_file_close(layout_file); + } else { + bad_kb_script_set_default_keyboard_layout(bad_kb); + } + storage_file_free(layout_file); +} + +void bad_kb_script_start_stop(BadKbScript* bad_kb) { + furi_assert(bad_kb); + furi_thread_flags_set(furi_thread_get_id(bad_kb->thread), WorkerEvtStartStop); +} + +void bad_kb_script_pause_resume(BadKbScript* bad_kb) { + furi_assert(bad_kb); + furi_thread_flags_set(furi_thread_get_id(bad_kb->thread), WorkerEvtPauseResume); +} + +BadKbState* bad_kb_script_get_state(BadKbScript* bad_kb) { + furi_assert(bad_kb); + return &(bad_kb->st); +} diff --git a/applications/external/bad_kb/helpers/ducky_script.h b/applications/external/bad_kb/helpers/ducky_script.h new file mode 100644 index 00000000000..35861790307 --- /dev/null +++ b/applications/external/bad_kb/helpers/ducky_script.h @@ -0,0 +1,181 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +#include +#include +#include +#include +#include +#include "../views/bad_kb_view.h" +#include "../bad_kb_paths.h" + +#define FILE_BUFFER_LEN 16 + +typedef enum { + LevelRssi122_100, + LevelRssi99_80, + LevelRssi79_60, + LevelRssi59_40, + LevelRssi39_0, + LevelRssiNum, + LevelRssiError = 0xFF, +} LevelRssiRange; + +extern const uint8_t bt_hid_delays[LevelRssiNum]; + +extern uint8_t bt_timeout; + +typedef enum { + BadKbStateInit, + BadKbStateNotConnected, + BadKbStateIdle, + BadKbStateWillRun, + BadKbStateRunning, + BadKbStateDelay, + BadKbStateStringDelay, + BadKbStateWaitForBtn, + BadKbStatePaused, + BadKbStateDone, + BadKbStateScriptError, + BadKbStateFileError, +} BadKbWorkerState; + +struct BadKbState { + BadKbWorkerState state; + bool is_bt; + uint32_t pin; + uint16_t line_cur; + uint16_t line_nb; + uint32_t delay_remain; + uint16_t error_line; + char error[64]; +}; + +typedef struct BadKbApp BadKbApp; + +typedef struct { + FuriThread* thread; + BadKbState st; + + FuriString* file_path; + FuriString* keyboard_layout; + uint8_t file_buf[FILE_BUFFER_LEN + 1]; + uint8_t buf_start; + uint8_t buf_len; + bool file_end; + + uint32_t defdelay; + uint32_t stringdelay; + uint16_t layout[128]; + + FuriString* line; + FuriString* line_prev; + uint32_t repeat_cnt; + uint8_t key_hold_nb; + + FuriString* string_print; + size_t string_print_pos; + + Bt* bt; + BadKbApp* app; +} BadKbScript; + +BadKbScript* bad_kb_script_open(FuriString* file_path, Bt* bt, BadKbApp* app); + +void bad_kb_script_close(BadKbScript* bad_kb); + +void bad_kb_script_set_keyboard_layout(BadKbScript* bad_kb, FuriString* layout_path); + +void bad_kb_script_start(BadKbScript* bad_kb); + +void bad_kb_script_stop(BadKbScript* bad_kb); + +void bad_kb_script_start_stop(BadKbScript* bad_kb); + +void bad_kb_script_pause_resume(BadKbScript* bad_kb); + +BadKbState* bad_kb_script_get_state(BadKbScript* bad_kb); + +#define BAD_KB_NAME_LEN FURI_HAL_BT_ADV_NAME_LENGTH +#define BAD_KB_MAC_LEN GAP_MAC_ADDR_SIZE +#define BAD_KB_USB_LEN HID_MANUF_PRODUCT_NAME_LEN + +extern const uint8_t BAD_KB_EMPTY_MAC[BAD_KB_MAC_LEN]; +extern uint8_t BAD_KB_BOUND_MAC[BAD_KB_MAC_LEN]; // For remember mode + +typedef enum { + BadKbAppErrorNoFiles, +} BadKbAppError; + +typedef struct { + char bt_name[BAD_KB_NAME_LEN]; + uint8_t bt_mac[BAD_KB_MAC_LEN]; + FuriHalUsbHidConfig usb_cfg; +} BadKbConfig; + +typedef enum { + BadKbConnModeNone, + BadKbConnModeUsb, + BadKbConnModeBt, +} BadKbConnMode; + +struct BadKbApp { + Gui* gui; + ViewDispatcher* view_dispatcher; + SceneManager* scene_manager; + NotificationApp* notifications; + DialogsApp* dialogs; + Widget* widget; + VariableItemList* var_item_list; + TextInput* text_input; + ByteInput* byte_input; + char usb_name_buf[BAD_KB_USB_LEN]; + uint16_t usb_vidpid_buf[2]; + char bt_name_buf[BAD_KB_NAME_LEN]; + uint8_t bt_mac_buf[BAD_KB_MAC_LEN]; + + BadKbAppError error; + FuriString* file_path; + FuriString* keyboard_layout; + BadKb* bad_kb_view; + BadKbScript* bad_kb_script; + + Bt* bt; + bool is_bt; + bool bt_remember; + BadKbConfig config; // User options + BadKbConfig id_config; // ID and BT_ID values + + bool set_usb_id; + bool set_bt_id; + bool has_usb_id; + bool has_bt_id; + + GapPairing prev_bt_mode; + char prev_bt_name[BAD_KB_NAME_LEN]; + uint8_t prev_bt_mac[BAD_KB_MAC_LEN]; + FuriHalUsbInterface* prev_usb_mode; + + FuriHalUsbHidConfig* hid_cfg; + BadKbConnMode conn_mode; + FuriThread* conn_init_thread; +}; + +int32_t bad_kb_conn_apply(BadKbApp* app); + +void bad_kb_conn_reset(BadKbApp* app); + +void bad_kb_config_refresh(BadKbApp* app); + +void bad_kb_config_adjust(BadKbConfig* cfg); + +#ifdef __cplusplus +} +#endif diff --git a/applications/external/bad_kb/helpers/ducky_script_commands.c b/applications/external/bad_kb/helpers/ducky_script_commands.c new file mode 100644 index 00000000000..5bae8fc24e0 --- /dev/null +++ b/applications/external/bad_kb/helpers/ducky_script_commands.c @@ -0,0 +1,213 @@ +#include +#include +#include +#include "ducky_script.h" +#include "ducky_script_i.h" + +typedef int32_t (*DuckyCmdCallback)(BadKbScript* bad_kb, const char* line, int32_t param); + +typedef struct { + char* name; + DuckyCmdCallback callback; + int32_t param; +} DuckyCmd; + +static int32_t ducky_fnc_delay(BadKbScript* bad_kb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + uint32_t delay_val = 0; + bool state = ducky_get_number(line, &delay_val); + if((state) && (delay_val > 0)) { + return (int32_t)delay_val; + } + + return ducky_error(bad_kb, "Invalid number %s", line); +} + +static int32_t ducky_fnc_defdelay(BadKbScript* bad_kb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + bool state = ducky_get_number(line, &bad_kb->defdelay); + if(!state) { + return ducky_error(bad_kb, "Invalid number %s", line); + } + return 0; +} + +static int32_t ducky_fnc_strdelay(BadKbScript* bad_kb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + bool state = ducky_get_number(line, &bad_kb->stringdelay); + if(!state) { + return ducky_error(bad_kb, "Invalid number %s", line); + } + return 0; +} + +static int32_t ducky_fnc_string(BadKbScript* bad_kb, const char* line, int32_t param) { + line = &line[ducky_get_command_len(line) + 1]; + furi_string_set_str(bad_kb->string_print, line); + if(param == 1) { + furi_string_cat(bad_kb->string_print, "\n"); + } + + if(bad_kb->stringdelay == 0) { // stringdelay not set - run command immidiately + bool state = ducky_string(bad_kb, furi_string_get_cstr(bad_kb->string_print)); + if(!state) { + return ducky_error(bad_kb, "Invalid string %s", line); + } + } else { // stringdelay is set - run command in thread to keep handling external events + return SCRIPT_STATE_STRING_START; + } + + return 0; +} + +static int32_t ducky_fnc_repeat(BadKbScript* bad_kb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + bool state = ducky_get_number(line, &bad_kb->repeat_cnt); + if((!state) || (bad_kb->repeat_cnt == 0)) { + return ducky_error(bad_kb, "Invalid number %s", line); + } + return 0; +} + +static int32_t ducky_fnc_sysrq(BadKbScript* bad_kb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + uint16_t key = ducky_get_keycode(bad_kb, line, true); + if(bad_kb->bt) { + furi_hal_bt_hid_kb_press(KEY_MOD_LEFT_ALT | HID_KEYBOARD_PRINT_SCREEN); + furi_hal_bt_hid_kb_press(key); + furi_delay_ms(bt_timeout); + furi_hal_bt_hid_kb_release_all(); + } else { + 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; +} + +static int32_t ducky_fnc_altchar(BadKbScript* bad_kb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + ducky_numlock_on(bad_kb); + bool state = ducky_altchar(bad_kb, line); + if(!state) { + return ducky_error(bad_kb, "Invalid altchar %s", line); + } + return 0; +} + +static int32_t ducky_fnc_altstring(BadKbScript* bad_kb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + ducky_numlock_on(bad_kb); + bool state = ducky_altstring(bad_kb, line); + if(!state) { + return ducky_error(bad_kb, "Invalid altstring %s", line); + } + return 0; +} + +static int32_t ducky_fnc_hold(BadKbScript* bad_kb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + uint16_t key = ducky_get_keycode(bad_kb, line, true); + if(key == HID_KEYBOARD_NONE) { + return ducky_error(bad_kb, "No keycode defined for %s", line); + } + bad_kb->key_hold_nb++; + if(bad_kb->key_hold_nb > (HID_KB_MAX_KEYS - 1)) { + return ducky_error(bad_kb, "Too many keys are hold"); + } + if(bad_kb->bt) { + furi_hal_bt_hid_kb_press(key); + } else { + furi_hal_hid_kb_press(key); + } + return 0; +} + +static int32_t ducky_fnc_release(BadKbScript* bad_kb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + uint16_t key = ducky_get_keycode(bad_kb, line, true); + if(key == HID_KEYBOARD_NONE) { + return ducky_error(bad_kb, "No keycode defined for %s", line); + } + if(bad_kb->key_hold_nb == 0) { + return ducky_error(bad_kb, "No keys are hold"); + } + bad_kb->key_hold_nb--; + if(bad_kb->bt) { + furi_hal_bt_hid_kb_release(key); + } else { + furi_hal_hid_kb_release(key); + } + return 0; +} + +static int32_t ducky_fnc_waitforbutton(BadKbScript* bad_kb, const char* line, int32_t param) { + UNUSED(param); + UNUSED(bad_kb); + UNUSED(line); + + return SCRIPT_STATE_WAIT_FOR_BTN; +} + +static const DuckyCmd ducky_commands[] = { + {"REM", NULL, -1}, + {"ID", NULL, -1}, + {"BT_ID", NULL, -1}, + {"DELAY", ducky_fnc_delay, -1}, + {"STRING", ducky_fnc_string, 0}, + {"STRINGLN", ducky_fnc_string, 1}, + {"DEFAULT_DELAY", ducky_fnc_defdelay, -1}, + {"DEFAULTDELAY", ducky_fnc_defdelay, -1}, + {"STRINGDELAY", ducky_fnc_strdelay, -1}, + {"STRING_DELAY", ducky_fnc_strdelay, -1}, + {"REPEAT", ducky_fnc_repeat, -1}, + {"SYSRQ", ducky_fnc_sysrq, -1}, + {"ALTCHAR", ducky_fnc_altchar, -1}, + {"ALTSTRING", ducky_fnc_altstring, -1}, + {"ALTCODE", ducky_fnc_altstring, -1}, + {"HOLD", ducky_fnc_hold, -1}, + {"RELEASE", ducky_fnc_release, -1}, + {"WAIT_FOR_BUTTON_PRESS", ducky_fnc_waitforbutton, -1}, +}; + +#define TAG "BadKB" +#define WORKER_TAG TAG "Worker" + +int32_t ducky_execute_cmd(BadKbScript* bad_kb, const char* line) { + size_t cmd_word_len = strcspn(line, " "); + for(size_t i = 0; i < COUNT_OF(ducky_commands); i++) { + size_t cmd_compare_len = strlen(ducky_commands[i].name); + + if(cmd_compare_len != cmd_word_len) { + continue; + } + + if(strncmp(line, ducky_commands[i].name, cmd_compare_len) == 0) { + if(ducky_commands[i].callback == NULL) { + return 0; + } else { + return ((ducky_commands[i].callback)(bad_kb, line, ducky_commands[i].param)); + } + } + } + + return SCRIPT_STATE_CMD_UNKNOWN; +} diff --git a/applications/external/bad_kb/helpers/ducky_script_i.h b/applications/external/bad_kb/helpers/ducky_script_i.h new file mode 100644 index 00000000000..646355c9c6f --- /dev/null +++ b/applications/external/bad_kb/helpers/ducky_script_i.h @@ -0,0 +1,44 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include "ducky_script.h" + +#define SCRIPT_STATE_ERROR (-1) +#define SCRIPT_STATE_END (-2) +#define SCRIPT_STATE_NEXT_LINE (-3) +#define SCRIPT_STATE_CMD_UNKNOWN (-4) +#define SCRIPT_STATE_STRING_START (-5) +#define SCRIPT_STATE_WAIT_FOR_BTN (-6) + +uint16_t ducky_get_keycode(BadKbScript* bad_kb, const char* param, bool accept_chars); + +uint32_t ducky_get_command_len(const char* line); + +bool ducky_is_line_end(const char chr); + +uint16_t ducky_get_keycode_by_name(const char* param); + +bool ducky_get_number(const char* param, uint32_t* val); + +void ducky_numlock_on(BadKbScript* bad_kb); + +bool ducky_numpad_press(BadKbScript* bad_kb, const char num); + +bool ducky_altchar(BadKbScript* bad_kb, const char* charcode); + +bool ducky_altstring(BadKbScript* bad_kb, const char* param); + +bool ducky_string(BadKbScript* bad_kb, const char* param); + +int32_t ducky_execute_cmd(BadKbScript* bad_kb, const char* line); + +int32_t ducky_error(BadKbScript* bad_kb, const char* text, ...); + +#ifdef __cplusplus +} +#endif diff --git a/applications/external/bad_kb/helpers/ducky_script_keycodes.c b/applications/external/bad_kb/helpers/ducky_script_keycodes.c new file mode 100644 index 00000000000..da2fc22f79f --- /dev/null +++ b/applications/external/bad_kb/helpers/ducky_script_keycodes.c @@ -0,0 +1,79 @@ +#include +#include +#include "ducky_script_i.h" + +typedef struct { + char* name; + uint16_t keycode; +} DuckyKey; + +static const DuckyKey ducky_keys[] = { + {"CTRL-ALT", KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_ALT}, + {"CTRL-SHIFT", KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT}, + {"ALT-SHIFT", KEY_MOD_LEFT_ALT | KEY_MOD_LEFT_SHIFT}, + {"ALT-GUI", KEY_MOD_LEFT_ALT | KEY_MOD_LEFT_GUI}, + {"GUI-SHIFT", KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT}, + {"GUI-CTRL", KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_CTRL}, + + {"CTRL", KEY_MOD_LEFT_CTRL}, + {"CONTROL", KEY_MOD_LEFT_CTRL}, + {"SHIFT", KEY_MOD_LEFT_SHIFT}, + {"ALT", KEY_MOD_LEFT_ALT}, + {"GUI", KEY_MOD_LEFT_GUI}, + {"WINDOWS", KEY_MOD_LEFT_GUI}, + + {"DOWNARROW", HID_KEYBOARD_DOWN_ARROW}, + {"DOWN", HID_KEYBOARD_DOWN_ARROW}, + {"LEFTARROW", HID_KEYBOARD_LEFT_ARROW}, + {"LEFT", HID_KEYBOARD_LEFT_ARROW}, + {"RIGHTARROW", HID_KEYBOARD_RIGHT_ARROW}, + {"RIGHT", HID_KEYBOARD_RIGHT_ARROW}, + {"UPARROW", HID_KEYBOARD_UP_ARROW}, + {"UP", HID_KEYBOARD_UP_ARROW}, + + {"ENTER", HID_KEYBOARD_RETURN}, + {"BREAK", HID_KEYBOARD_PAUSE}, + {"PAUSE", HID_KEYBOARD_PAUSE}, + {"CAPSLOCK", HID_KEYBOARD_CAPS_LOCK}, + {"DELETE", HID_KEYBOARD_DELETE_FORWARD}, + {"BACKSPACE", HID_KEYBOARD_DELETE}, + {"END", HID_KEYBOARD_END}, + {"ESC", HID_KEYBOARD_ESCAPE}, + {"ESCAPE", HID_KEYBOARD_ESCAPE}, + {"HOME", HID_KEYBOARD_HOME}, + {"INSERT", HID_KEYBOARD_INSERT}, + {"NUMLOCK", HID_KEYPAD_NUMLOCK}, + {"PAGEUP", HID_KEYBOARD_PAGE_UP}, + {"PAGEDOWN", HID_KEYBOARD_PAGE_DOWN}, + {"PRINTSCREEN", HID_KEYBOARD_PRINT_SCREEN}, + {"SCROLLLOCK", HID_KEYBOARD_SCROLL_LOCK}, + {"SPACE", HID_KEYBOARD_SPACEBAR}, + {"TAB", HID_KEYBOARD_TAB}, + {"MENU", HID_KEYBOARD_APPLICATION}, + {"APP", HID_KEYBOARD_APPLICATION}, + + {"F1", HID_KEYBOARD_F1}, + {"F2", HID_KEYBOARD_F2}, + {"F3", HID_KEYBOARD_F3}, + {"F4", HID_KEYBOARD_F4}, + {"F5", HID_KEYBOARD_F5}, + {"F6", HID_KEYBOARD_F6}, + {"F7", HID_KEYBOARD_F7}, + {"F8", HID_KEYBOARD_F8}, + {"F9", HID_KEYBOARD_F9}, + {"F10", HID_KEYBOARD_F10}, + {"F11", HID_KEYBOARD_F11}, + {"F12", HID_KEYBOARD_F12}, +}; + +uint16_t ducky_get_keycode_by_name(const char* param) { + for(size_t i = 0; i < COUNT_OF(ducky_keys); i++) { + size_t key_cmd_len = strlen(ducky_keys[i].name); + if((strncmp(param, ducky_keys[i].name, key_cmd_len) == 0) && + (ducky_is_line_end(param[key_cmd_len]))) { + return ducky_keys[i].keycode; + } + } + + return HID_KEYBOARD_NONE; +} diff --git a/applications/external/bad_kb/icon.png b/applications/external/bad_kb/icon.png new file mode 100644 index 00000000000..037474aa3bc Binary files /dev/null and b/applications/external/bad_kb/icon.png differ diff --git a/applications/external/bad_kb/images/Clock_18x18.png b/applications/external/bad_kb/images/Clock_18x18.png new file mode 100644 index 00000000000..ab06d008eeb Binary files /dev/null and b/applications/external/bad_kb/images/Clock_18x18.png differ diff --git a/applications/external/bad_kb/images/Error_18x18.png b/applications/external/bad_kb/images/Error_18x18.png new file mode 100644 index 00000000000..16a5a74d966 Binary files /dev/null and b/applications/external/bad_kb/images/Error_18x18.png differ diff --git a/applications/external/bad_kb/images/EviSmile1_18x21.png b/applications/external/bad_kb/images/EviSmile1_18x21.png new file mode 100644 index 00000000000..987af32587c Binary files /dev/null and b/applications/external/bad_kb/images/EviSmile1_18x21.png differ diff --git a/applications/external/bad_kb/images/EviSmile2_18x21.png b/applications/external/bad_kb/images/EviSmile2_18x21.png new file mode 100644 index 00000000000..7e28c9f018a Binary files /dev/null and b/applications/external/bad_kb/images/EviSmile2_18x21.png differ diff --git a/applications/external/bad_kb/images/EviWaiting1_18x21.png b/applications/external/bad_kb/images/EviWaiting1_18x21.png new file mode 100644 index 00000000000..d39d2173329 Binary files /dev/null and b/applications/external/bad_kb/images/EviWaiting1_18x21.png differ diff --git a/applications/external/bad_kb/images/EviWaiting2_18x21.png b/applications/external/bad_kb/images/EviWaiting2_18x21.png new file mode 100644 index 00000000000..15ca088fd73 Binary files /dev/null and b/applications/external/bad_kb/images/EviWaiting2_18x21.png differ diff --git a/applications/external/bad_kb/images/Percent_10x14.png b/applications/external/bad_kb/images/Percent_10x14.png new file mode 100644 index 00000000000..677911fd44d Binary files /dev/null and b/applications/external/bad_kb/images/Percent_10x14.png differ diff --git a/applications/external/bad_kb/images/SDQuestion_35x43.png b/applications/external/bad_kb/images/SDQuestion_35x43.png new file mode 100644 index 00000000000..9b9c9a58e32 Binary files /dev/null and b/applications/external/bad_kb/images/SDQuestion_35x43.png differ diff --git a/applications/external/bad_kb/images/Smile_18x18.png b/applications/external/bad_kb/images/Smile_18x18.png new file mode 100644 index 00000000000..d2aae0dc37f Binary files /dev/null and b/applications/external/bad_kb/images/Smile_18x18.png differ diff --git a/applications/external/bad_kb/images/UsbTree_48x22.png b/applications/external/bad_kb/images/UsbTree_48x22.png new file mode 100644 index 00000000000..cc41b5b9a91 Binary files /dev/null and b/applications/external/bad_kb/images/UsbTree_48x22.png differ diff --git a/applications/external/bad_kb/images/badkb_10px.png b/applications/external/bad_kb/images/badkb_10px.png new file mode 100644 index 00000000000..037474aa3bc Binary files /dev/null and b/applications/external/bad_kb/images/badkb_10px.png differ diff --git a/applications/external/bad_kb/images/keyboard_10px.png b/applications/external/bad_kb/images/keyboard_10px.png new file mode 100644 index 00000000000..74a10e6db2e Binary files /dev/null and b/applications/external/bad_kb/images/keyboard_10px.png differ diff --git a/applications/external/bad_kb/scenes/bad_kb_scene.c b/applications/external/bad_kb/scenes/bad_kb_scene.c new file mode 100644 index 00000000000..f90d23a7774 --- /dev/null +++ b/applications/external/bad_kb/scenes/bad_kb_scene.c @@ -0,0 +1,30 @@ +#include "bad_kb_scene.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const bad_kb_scene_on_enter_handlers[])(void*) = { +#include "bad_kb_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_event handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event, +bool (*const bad_kb_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "bad_kb_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_exit handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit, +void (*const bad_kb_scene_on_exit_handlers[])(void* context) = { +#include "bad_kb_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers bad_kb_scene_handlers = { + .on_enter_handlers = bad_kb_scene_on_enter_handlers, + .on_event_handlers = bad_kb_scene_on_event_handlers, + .on_exit_handlers = bad_kb_scene_on_exit_handlers, + .scene_num = BadKbSceneNum, +}; diff --git a/applications/external/bad_kb/scenes/bad_kb_scene.h b/applications/external/bad_kb/scenes/bad_kb_scene.h new file mode 100644 index 00000000000..82db02873db --- /dev/null +++ b/applications/external/bad_kb/scenes/bad_kb_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) BadKbScene##id, +typedef enum { +#include "bad_kb_scene_config.h" + BadKbSceneNum, +} BadKbScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers bad_kb_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "bad_kb_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_event handlers declaration +#define ADD_SCENE(prefix, name, id) \ + bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event); +#include "bad_kb_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_exit handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context); +#include "bad_kb_scene_config.h" +#undef ADD_SCENE diff --git a/applications/external/bad_kb/scenes/bad_kb_scene_config.c b/applications/external/bad_kb/scenes/bad_kb_scene_config.c new file mode 100644 index 00000000000..b882a4477a3 --- /dev/null +++ b/applications/external/bad_kb/scenes/bad_kb_scene_config.c @@ -0,0 +1,187 @@ +#include "../bad_kb_app.h" +#include "../helpers/ducky_script.h" +#include "furi_hal_power.h" +#include "furi_hal_usb.h" + +enum VarItemListIndex { + VarItemListIndexKeyboardLayout, + VarItemListIndexConnection, +}; + +enum VarItemListIndexBt { + VarItemListIndexBtRemember = VarItemListIndexConnection + 1, + VarItemListIndexBtDeviceName, + VarItemListIndexBtMacAddress, + VarItemListIndexBtRandomizeMac, +}; + +enum VarItemListIndexUsb { + VarItemListIndexUsbManufacturer = VarItemListIndexConnection + 1, + VarItemListIndexUsbProductName, + VarItemListIndexUsbVidPid, + VarItemListIndexUsbRandomizeVidPid, +}; + +void bad_kb_scene_config_connection_callback(VariableItem* item) { + BadKbApp* bad_kb = variable_item_get_context(item); + bad_kb->is_bt = variable_item_get_current_value_index(item); + variable_item_set_current_value_text(item, bad_kb->is_bt ? "BT" : "USB"); + view_dispatcher_send_custom_event(bad_kb->view_dispatcher, VarItemListIndexConnection); +} + +void bad_kb_scene_config_bt_remember_callback(VariableItem* item) { + BadKbApp* bad_kb = variable_item_get_context(item); + bad_kb->bt_remember = variable_item_get_current_value_index(item); + variable_item_set_current_value_text(item, bad_kb->bt_remember ? "ON" : "OFF"); + view_dispatcher_send_custom_event(bad_kb->view_dispatcher, VarItemListIndexBtRemember); +} + +void bad_kb_scene_config_var_item_list_callback(void* context, uint32_t index) { + BadKbApp* bad_kb = context; + view_dispatcher_send_custom_event(bad_kb->view_dispatcher, index); +} + +void bad_kb_scene_config_on_enter(void* context) { + BadKbApp* bad_kb = context; + VariableItemList* var_item_list = bad_kb->var_item_list; + VariableItem* item; + + item = variable_item_list_add(var_item_list, "Keyboard layout", 0, NULL, bad_kb); + + item = variable_item_list_add( + var_item_list, "Connection", 2, bad_kb_scene_config_connection_callback, bad_kb); + variable_item_set_current_value_index(item, bad_kb->is_bt); + variable_item_set_current_value_text(item, bad_kb->is_bt ? "BT" : "USB"); + if(bad_kb->has_usb_id) { + variable_item_set_locked(item, true, "Script has\nID cmd!\nLocked to\nUSB Mode!"); + } else if(bad_kb->has_bt_id) { + variable_item_set_locked(item, true, "Script has\nBT_ID cmd!\nLocked to\nBT Mode!"); + } + + if(bad_kb->is_bt) { + item = variable_item_list_add( + var_item_list, "BT Remember", 2, bad_kb_scene_config_bt_remember_callback, bad_kb); + variable_item_set_current_value_index(item, bad_kb->bt_remember); + variable_item_set_current_value_text(item, bad_kb->bt_remember ? "ON" : "OFF"); + + item = variable_item_list_add(var_item_list, "BT Device Name", 0, NULL, bad_kb); + if(bad_kb->set_bt_id) { + variable_item_set_locked(item, true, "Script has\nBT_ID cmd!\nLocked to\nset Name!"); + } + + item = variable_item_list_add(var_item_list, "BT MAC Address", 0, NULL, bad_kb); + if(bad_kb->bt_remember) { + variable_item_set_locked(item, true, "Remember\nmust be Off!"); + } else if(bad_kb->set_bt_id) { + variable_item_set_locked(item, true, "Script has\nBT_ID cmd!\nLocked to\nset MAC!"); + } + + item = variable_item_list_add(var_item_list, "Randomize BT MAC", 0, NULL, bad_kb); + if(bad_kb->bt_remember) { + variable_item_set_locked(item, true, "Remember\nmust be Off!"); + } else if(bad_kb->set_bt_id) { + variable_item_set_locked(item, true, "Script has\nBT_ID cmd!\nLocked to\nset MAC!"); + } + } else { + item = variable_item_list_add(var_item_list, "USB Manufacturer", 0, NULL, bad_kb); + if(bad_kb->set_usb_id) { + variable_item_set_locked(item, true, "Script has\nID cmd!\nLocked to\nset Mname!"); + } + + item = variable_item_list_add(var_item_list, "USB Product Name", 0, NULL, bad_kb); + if(bad_kb->set_usb_id) { + variable_item_set_locked(item, true, "Script has\nID cmd!\nLocked to\nset Pname!"); + } + + item = variable_item_list_add(var_item_list, "USB VID and PID", 0, NULL, bad_kb); + if(bad_kb->set_usb_id) { + variable_item_set_locked(item, true, "Script has\nID cmd!\nLocked to\nset IDs!"); + } + + item = variable_item_list_add(var_item_list, "Randomize USB VID:PID", 0, NULL, bad_kb); + if(bad_kb->set_usb_id) { + variable_item_set_locked(item, true, "Script has\nID cmd!\nLocked to\nset IDs!"); + } + } + + variable_item_list_set_enter_callback( + var_item_list, bad_kb_scene_config_var_item_list_callback, bad_kb); + + variable_item_list_set_selected_item( + var_item_list, scene_manager_get_scene_state(bad_kb->scene_manager, BadKbSceneConfig)); + + view_dispatcher_switch_to_view(bad_kb->view_dispatcher, BadKbAppViewVarItemList); +} + +bool bad_kb_scene_config_on_event(void* context, SceneManagerEvent event) { + BadKbApp* bad_kb = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_set_scene_state(bad_kb->scene_manager, BadKbSceneConfig, event.event); + consumed = true; + switch(event.event) { + case VarItemListIndexKeyboardLayout: + scene_manager_next_scene(bad_kb->scene_manager, BadKbSceneConfigLayout); + break; + case VarItemListIndexConnection: + bad_kb_config_refresh(bad_kb); + break; + default: + break; + } + if(bad_kb->is_bt) { + switch(event.event) { + case VarItemListIndexBtRemember: + bad_kb_config_refresh(bad_kb); + break; + case VarItemListIndexBtDeviceName: + scene_manager_next_scene(bad_kb->scene_manager, BadKbSceneConfigBtName); + break; + case VarItemListIndexBtMacAddress: + scene_manager_next_scene(bad_kb->scene_manager, BadKbSceneConfigBtMac); + break; + case VarItemListIndexBtRandomizeMac: + furi_hal_random_fill_buf(bad_kb->config.bt_mac, BAD_KB_MAC_LEN); + bad_kb_config_refresh(bad_kb); + break; + default: + break; + } + } else { + switch(event.event) { + case VarItemListIndexUsbManufacturer: + scene_manager_set_scene_state( + bad_kb->scene_manager, BadKbSceneConfigUsbName, true); + scene_manager_next_scene(bad_kb->scene_manager, BadKbSceneConfigUsbName); + break; + case VarItemListIndexUsbProductName: + scene_manager_set_scene_state( + bad_kb->scene_manager, BadKbSceneConfigUsbName, false); + scene_manager_next_scene(bad_kb->scene_manager, BadKbSceneConfigUsbName); + break; + case VarItemListIndexUsbVidPid: + scene_manager_next_scene(bad_kb->scene_manager, BadKbSceneConfigUsbVidPid); + break; + case VarItemListIndexUsbRandomizeVidPid: + furi_hal_random_fill_buf( + (void*)bad_kb->usb_vidpid_buf, sizeof(bad_kb->usb_vidpid_buf)); + bad_kb->config.usb_cfg.vid = bad_kb->usb_vidpid_buf[0]; + bad_kb->config.usb_cfg.pid = bad_kb->usb_vidpid_buf[1]; + bad_kb_config_refresh(bad_kb); + break; + default: + break; + } + } + } + + return consumed; +} + +void bad_kb_scene_config_on_exit(void* context) { + BadKbApp* bad_kb = context; + VariableItemList* var_item_list = bad_kb->var_item_list; + + variable_item_list_reset(var_item_list); +} diff --git a/applications/external/bad_kb/scenes/bad_kb_scene_config.h b/applications/external/bad_kb/scenes/bad_kb_scene_config.h new file mode 100644 index 00000000000..034a898a44d --- /dev/null +++ b/applications/external/bad_kb/scenes/bad_kb_scene_config.h @@ -0,0 +1,9 @@ +ADD_SCENE(bad_kb, file_select, FileSelect) +ADD_SCENE(bad_kb, work, Work) +ADD_SCENE(bad_kb, error, Error) +ADD_SCENE(bad_kb, config, Config) +ADD_SCENE(bad_kb, config_layout, ConfigLayout) +ADD_SCENE(bad_kb, config_bt_name, ConfigBtName) +ADD_SCENE(bad_kb, config_bt_mac, ConfigBtMac) +ADD_SCENE(bad_kb, config_usb_name, ConfigUsbName) +ADD_SCENE(bad_kb, config_usb_vidpid, ConfigUsbVidPid) diff --git a/applications/external/bad_kb/scenes/bad_kb_scene_config_bt_mac.c b/applications/external/bad_kb/scenes/bad_kb_scene_config_bt_mac.c new file mode 100644 index 00000000000..e60d6862281 --- /dev/null +++ b/applications/external/bad_kb/scenes/bad_kb_scene_config_bt_mac.c @@ -0,0 +1,50 @@ +#include "../bad_kb_app.h" + +void bad_kb_scene_config_bt_mac_byte_input_callback(void* context) { + BadKbApp* bad_kb = context; + + view_dispatcher_send_custom_event(bad_kb->view_dispatcher, BadKbAppCustomEventByteInputDone); +} + +void bad_kb_scene_config_bt_mac_on_enter(void* context) { + BadKbApp* bad_kb = context; + ByteInput* byte_input = bad_kb->byte_input; + + memmove(bad_kb->bt_mac_buf, bad_kb->config.bt_mac, BAD_KB_MAC_LEN); + furi_hal_bt_reverse_mac_addr(bad_kb->bt_mac_buf); + byte_input_set_header_text(byte_input, "Set BT MAC address"); + + byte_input_set_result_callback( + byte_input, + bad_kb_scene_config_bt_mac_byte_input_callback, + NULL, + bad_kb, + bad_kb->bt_mac_buf, + BAD_KB_MAC_LEN); + + view_dispatcher_switch_to_view(bad_kb->view_dispatcher, BadKbAppViewByteInput); +} + +bool bad_kb_scene_config_bt_mac_on_event(void* context, SceneManagerEvent event) { + BadKbApp* bad_kb = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + if(event.event == BadKbAppCustomEventByteInputDone) { + furi_hal_bt_reverse_mac_addr(bad_kb->bt_mac_buf); + memmove(bad_kb->config.bt_mac, bad_kb->bt_mac_buf, BAD_KB_MAC_LEN); + bad_kb_config_refresh(bad_kb); + } + scene_manager_previous_scene(bad_kb->scene_manager); + } + return consumed; +} + +void bad_kb_scene_config_bt_mac_on_exit(void* context) { + BadKbApp* bad_kb = context; + ByteInput* byte_input = bad_kb->byte_input; + + byte_input_set_result_callback(byte_input, NULL, NULL, NULL, NULL, 0); + byte_input_set_header_text(byte_input, ""); +} diff --git a/applications/external/bad_kb/scenes/bad_kb_scene_config_bt_name.c b/applications/external/bad_kb/scenes/bad_kb_scene_config_bt_name.c new file mode 100644 index 00000000000..3ce2c99b4a2 --- /dev/null +++ b/applications/external/bad_kb/scenes/bad_kb_scene_config_bt_name.c @@ -0,0 +1,47 @@ +#include "../bad_kb_app.h" + +static void bad_kb_scene_config_bt_name_text_input_callback(void* context) { + BadKbApp* bad_kb = context; + + view_dispatcher_send_custom_event(bad_kb->view_dispatcher, BadKbAppCustomEventTextInputDone); +} + +void bad_kb_scene_config_bt_name_on_enter(void* context) { + BadKbApp* bad_kb = context; + TextInput* text_input = bad_kb->text_input; + + strlcpy(bad_kb->bt_name_buf, bad_kb->config.bt_name, BAD_KB_NAME_LEN); + text_input_set_header_text(text_input, "Set BT device name"); + + text_input_set_result_callback( + text_input, + bad_kb_scene_config_bt_name_text_input_callback, + bad_kb, + bad_kb->bt_name_buf, + BAD_KB_NAME_LEN, + true); + + view_dispatcher_switch_to_view(bad_kb->view_dispatcher, BadKbAppViewTextInput); +} + +bool bad_kb_scene_config_bt_name_on_event(void* context, SceneManagerEvent event) { + BadKbApp* bad_kb = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + if(event.event == BadKbAppCustomEventTextInputDone) { + strlcpy(bad_kb->config.bt_name, bad_kb->bt_name_buf, BAD_KB_NAME_LEN); + bad_kb_config_refresh(bad_kb); + } + scene_manager_previous_scene(bad_kb->scene_manager); + } + return consumed; +} + +void bad_kb_scene_config_bt_name_on_exit(void* context) { + BadKbApp* bad_kb = context; + TextInput* text_input = bad_kb->text_input; + + text_input_reset(text_input); +} diff --git a/applications/external/bad_kb/scenes/bad_kb_scene_config_layout.c b/applications/external/bad_kb/scenes/bad_kb_scene_config_layout.c new file mode 100644 index 00000000000..31d2b49ecf4 --- /dev/null +++ b/applications/external/bad_kb/scenes/bad_kb_scene_config_layout.c @@ -0,0 +1,48 @@ +#include "../bad_kb_app.h" +#include "furi_hal_power.h" +#include "furi_hal_usb.h" +#include + +static bool bad_kb_layout_select(BadKbApp* bad_kb) { + furi_assert(bad_kb); + + FuriString* predefined_path; + predefined_path = furi_string_alloc(); + if(!furi_string_empty(bad_kb->keyboard_layout)) { + furi_string_set(predefined_path, bad_kb->keyboard_layout); + } else { + furi_string_set(predefined_path, BAD_KB_APP_PATH_LAYOUT_FOLDER); + } + + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options( + &browser_options, BAD_KB_APP_LAYOUT_EXTENSION, &I_keyboard_10px); + browser_options.base_path = BAD_KB_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_kb->dialogs, bad_kb->keyboard_layout, predefined_path, &browser_options); + + furi_string_free(predefined_path); + return res; +} + +void bad_kb_scene_config_layout_on_enter(void* context) { + BadKbApp* bad_kb = context; + + if(bad_kb_layout_select(bad_kb)) { + bad_kb_script_set_keyboard_layout(bad_kb->bad_kb_script, bad_kb->keyboard_layout); + } + scene_manager_previous_scene(bad_kb->scene_manager); +} + +bool bad_kb_scene_config_layout_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + return false; +} + +void bad_kb_scene_config_layout_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/external/bad_kb/scenes/bad_kb_scene_config_usb_name.c b/applications/external/bad_kb/scenes/bad_kb_scene_config_usb_name.c new file mode 100644 index 00000000000..381c2cdf003 --- /dev/null +++ b/applications/external/bad_kb/scenes/bad_kb_scene_config_usb_name.c @@ -0,0 +1,56 @@ +#include "../bad_kb_app.h" + +static void bad_kb_scene_config_usb_name_text_input_callback(void* context) { + BadKbApp* bad_kb = context; + + view_dispatcher_send_custom_event(bad_kb->view_dispatcher, BadKbAppCustomEventTextInputDone); +} + +void bad_kb_scene_config_usb_name_on_enter(void* context) { + BadKbApp* bad_kb = context; + TextInput* text_input = bad_kb->text_input; + + if(scene_manager_get_scene_state(bad_kb->scene_manager, BadKbSceneConfigUsbName)) { + strlcpy(bad_kb->usb_name_buf, bad_kb->config.usb_cfg.manuf, BAD_KB_USB_LEN); + text_input_set_header_text(text_input, "Set USB manufacturer name"); + } else { + strlcpy(bad_kb->usb_name_buf, bad_kb->config.usb_cfg.product, BAD_KB_USB_LEN); + text_input_set_header_text(text_input, "Set USB product name"); + } + + text_input_set_result_callback( + text_input, + bad_kb_scene_config_usb_name_text_input_callback, + bad_kb, + bad_kb->usb_name_buf, + BAD_KB_USB_LEN, + true); + + view_dispatcher_switch_to_view(bad_kb->view_dispatcher, BadKbAppViewTextInput); +} + +bool bad_kb_scene_config_usb_name_on_event(void* context, SceneManagerEvent event) { + BadKbApp* bad_kb = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + if(event.event == BadKbAppCustomEventTextInputDone) { + if(scene_manager_get_scene_state(bad_kb->scene_manager, BadKbSceneConfigUsbName)) { + strlcpy(bad_kb->config.usb_cfg.manuf, bad_kb->usb_name_buf, BAD_KB_USB_LEN); + } else { + strlcpy(bad_kb->config.usb_cfg.product, bad_kb->usb_name_buf, BAD_KB_USB_LEN); + } + bad_kb_config_refresh(bad_kb); + } + scene_manager_previous_scene(bad_kb->scene_manager); + } + return consumed; +} + +void bad_kb_scene_config_usb_name_on_exit(void* context) { + BadKbApp* bad_kb = context; + TextInput* text_input = bad_kb->text_input; + + text_input_reset(text_input); +} diff --git a/applications/external/bad_kb/scenes/bad_kb_scene_config_usb_vidpid.c b/applications/external/bad_kb/scenes/bad_kb_scene_config_usb_vidpid.c new file mode 100644 index 00000000000..ef1a4700fc5 --- /dev/null +++ b/applications/external/bad_kb/scenes/bad_kb_scene_config_usb_vidpid.c @@ -0,0 +1,50 @@ +#include "../bad_kb_app.h" + +void bad_kb_scene_config_usb_vidpid_byte_input_callback(void* context) { + BadKbApp* bad_kb = context; + + view_dispatcher_send_custom_event(bad_kb->view_dispatcher, BadKbAppCustomEventByteInputDone); +} + +void bad_kb_scene_config_usb_vidpid_on_enter(void* context) { + BadKbApp* bad_kb = context; + ByteInput* byte_input = bad_kb->byte_input; + + bad_kb->usb_vidpid_buf[0] = __REVSH(bad_kb->config.usb_cfg.vid); + bad_kb->usb_vidpid_buf[1] = __REVSH(bad_kb->config.usb_cfg.pid); + byte_input_set_header_text(byte_input, "Set USB VID:PID"); + + byte_input_set_result_callback( + byte_input, + bad_kb_scene_config_usb_vidpid_byte_input_callback, + NULL, + bad_kb, + (void*)bad_kb->usb_vidpid_buf, + sizeof(bad_kb->usb_vidpid_buf)); + + view_dispatcher_switch_to_view(bad_kb->view_dispatcher, BadKbAppViewByteInput); +} + +bool bad_kb_scene_config_usb_vidpid_on_event(void* context, SceneManagerEvent event) { + BadKbApp* bad_kb = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + if(event.event == BadKbAppCustomEventByteInputDone) { + bad_kb->config.usb_cfg.vid = __REVSH(bad_kb->usb_vidpid_buf[0]); + bad_kb->config.usb_cfg.pid = __REVSH(bad_kb->usb_vidpid_buf[1]); + bad_kb_config_refresh(bad_kb); + } + scene_manager_previous_scene(bad_kb->scene_manager); + } + return consumed; +} + +void bad_kb_scene_config_usb_vidpid_on_exit(void* context) { + BadKbApp* bad_kb = context; + ByteInput* byte_input = bad_kb->byte_input; + + byte_input_set_result_callback(byte_input, NULL, NULL, NULL, NULL, 0); + byte_input_set_header_text(byte_input, ""); +} diff --git a/applications/external/bad_kb/scenes/bad_kb_scene_error.c b/applications/external/bad_kb/scenes/bad_kb_scene_error.c new file mode 100644 index 00000000000..85b404deb5f --- /dev/null +++ b/applications/external/bad_kb/scenes/bad_kb_scene_error.c @@ -0,0 +1,49 @@ +#include "../bad_kb_app.h" + +static void + bad_kb_scene_error_event_callback(GuiButtonType result, InputType type, void* context) { + furi_assert(context); + BadKbApp* app = context; + + if((result == GuiButtonTypeLeft) && (type == InputTypeShort)) { + view_dispatcher_send_custom_event(app->view_dispatcher, BadKbCustomEventErrorBack); + } +} + +void bad_kb_scene_error_on_enter(void* context) { + BadKbApp* app = context; + + if(app->error == BadKbAppErrorNoFiles) { + widget_add_icon_element(app->widget, 0, 0, &I_SDQuestion_35x43); + widget_add_string_multiline_element( + app->widget, + 81, + 4, + AlignCenter, + AlignTop, + FontSecondary, + "No app data found.\nThis app will not\nwork without\nrequired files."); + widget_add_button_element( + app->widget, GuiButtonTypeLeft, "Back", bad_kb_scene_error_event_callback, app); + } + + view_dispatcher_switch_to_view(app->view_dispatcher, BadKbAppViewWidget); +} + +bool bad_kb_scene_error_on_event(void* context, SceneManagerEvent event) { + BadKbApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == BadKbCustomEventErrorBack) { + view_dispatcher_stop(app->view_dispatcher); + consumed = true; + } + } + return consumed; +} + +void bad_kb_scene_error_on_exit(void* context) { + BadKbApp* app = context; + widget_reset(app->widget); +} diff --git a/applications/external/bad_kb/scenes/bad_kb_scene_file_select.c b/applications/external/bad_kb/scenes/bad_kb_scene_file_select.c new file mode 100644 index 00000000000..7fc3779d791 --- /dev/null +++ b/applications/external/bad_kb/scenes/bad_kb_scene_file_select.c @@ -0,0 +1,47 @@ +#include "../bad_kb_app.h" +#include + +static bool bad_kb_file_select(BadKbApp* bad_kb) { + furi_assert(bad_kb); + + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options( + &browser_options, BAD_KB_APP_SCRIPT_EXTENSION, &I_badkb_10px); + browser_options.base_path = BAD_KB_APP_BASE_FOLDER; + browser_options.skip_assets = true; + + // Input events and views are managed by file_browser + bool res = dialog_file_browser_show( + bad_kb->dialogs, bad_kb->file_path, bad_kb->file_path, &browser_options); + + return res; +} + +void bad_kb_scene_file_select_on_enter(void* context) { + BadKbApp* bad_kb = context; + + if(bad_kb->bad_kb_script) { + bad_kb_script_close(bad_kb->bad_kb_script); + bad_kb->bad_kb_script = NULL; + } + + if(bad_kb_file_select(bad_kb)) { + bad_kb->bad_kb_script = + bad_kb_script_open(bad_kb->file_path, bad_kb->is_bt ? bad_kb->bt : NULL, bad_kb); + bad_kb_script_set_keyboard_layout(bad_kb->bad_kb_script, bad_kb->keyboard_layout); + + scene_manager_next_scene(bad_kb->scene_manager, BadKbSceneWork); + } else { + view_dispatcher_stop(bad_kb->view_dispatcher); + } +} + +bool bad_kb_scene_file_select_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + return false; +} + +void bad_kb_scene_file_select_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/external/bad_kb/scenes/bad_kb_scene_work.c b/applications/external/bad_kb/scenes/bad_kb_scene_work.c new file mode 100644 index 00000000000..dda38889f41 --- /dev/null +++ b/applications/external/bad_kb/scenes/bad_kb_scene_work.c @@ -0,0 +1,59 @@ +#include "../helpers/ducky_script.h" +#include "../bad_kb_app.h" +#include "../views/bad_kb_view.h" +#include +#include "toolbox/path.h" + +void bad_kb_scene_work_button_callback(InputKey key, void* context) { + furi_assert(context); + BadKbApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, key); +} + +bool bad_kb_scene_work_on_event(void* context, SceneManagerEvent event) { + BadKbApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == InputKeyLeft) { + if(bad_kb_is_idle_state(app->bad_kb_view)) { + scene_manager_next_scene(app->scene_manager, BadKbSceneConfig); + } + consumed = true; + } else if(event.event == InputKeyOk) { + bad_kb_script_start_stop(app->bad_kb_script); + consumed = true; + } else if(event.event == InputKeyRight) { + bad_kb_script_pause_resume(app->bad_kb_script); + consumed = true; + } + } else if(event.type == SceneManagerEventTypeTick) { + bad_kb_set_state(app->bad_kb_view, bad_kb_script_get_state(app->bad_kb_script)); + } + return consumed; +} + +void bad_kb_scene_work_on_enter(void* context) { + BadKbApp* app = context; + + FuriString* file_name; + file_name = furi_string_alloc(); + path_extract_filename(app->file_path, file_name, true); + bad_kb_set_file_name(app->bad_kb_view, furi_string_get_cstr(file_name)); + furi_string_free(file_name); + + FuriString* layout; + layout = furi_string_alloc(); + path_extract_filename(app->keyboard_layout, layout, true); + bad_kb_set_layout(app->bad_kb_view, furi_string_get_cstr(layout)); + furi_string_free(layout); + + bad_kb_set_state(app->bad_kb_view, bad_kb_script_get_state(app->bad_kb_script)); + + bad_kb_set_button_callback(app->bad_kb_view, bad_kb_scene_work_button_callback, app); + view_dispatcher_switch_to_view(app->view_dispatcher, BadKbAppViewWork); +} + +void bad_kb_scene_work_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/external/bad_kb/views/bad_kb_view.c b/applications/external/bad_kb/views/bad_kb_view.c new file mode 100644 index 00000000000..86b68fea66e --- /dev/null +++ b/applications/external/bad_kb/views/bad_kb_view.c @@ -0,0 +1,280 @@ +#include "bad_kb_view.h" +#include "../helpers/ducky_script.h" +#include "../bad_kb_app.h" +#include +#include +#include "bad_kb_icons.h" + +#define MAX_NAME_LEN 64 + +typedef struct { + char file_name[MAX_NAME_LEN]; + char layout[MAX_NAME_LEN]; + BadKbState state; + bool pause_wait; + uint8_t anim_frame; +} BadKbModel; + +static void bad_kb_draw_callback(Canvas* canvas, void* _model) { + BadKbModel* model = _model; + BadKbWorkerState state = model->state.state; + + FuriString* disp_str = furi_string_alloc_set( + state == BadKbStateInit ? "( . . . )" : + model->state.is_bt ? "(BT) " : + "(USB) "); + furi_string_cat_str(disp_str, model->file_name); + 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_printf(disp_str, "(%s)", model->layout); + } + if(model->state.pin) { + furi_string_cat_printf(disp_str, " PIN: %ld", model->state.pin); + } + 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, 24, &I_UsbTree_48x22); + + if((state == BadKbStateIdle) || (state == BadKbStateDone) || + (state == BadKbStateNotConnected)) { + elements_button_center(canvas, "Run"); + elements_button_left(canvas, "Config"); + } else if((state == BadKbStateRunning) || (state == BadKbStateDelay)) { + elements_button_center(canvas, "Stop"); + if(!model->pause_wait) { + elements_button_right(canvas, "Pause"); + } + } else if(state == BadKbStatePaused) { + elements_button_center(canvas, "End"); + elements_button_right(canvas, "Resume"); + } else if(state == BadKbStateWaitForBtn) { + elements_button_center(canvas, "Press to continue"); + } else if(state == BadKbStateWillRun) { + elements_button_center(canvas, "Cancel"); + } + + if(state == BadKbStateNotConnected) { + canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Connect to"); + canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "a device"); + } else if(state == BadKbStateWillRun) { + canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Will run"); + canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "on connect"); + } else if(state == BadKbStateFileError) { + canvas_draw_icon(canvas, 4, 26, &I_Error_18x18); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "File"); + canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "ERROR"); + } else if(state == BadKbStateScriptError) { + 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); + furi_string_printf(disp_str, "line %u", model->state.error_line); + canvas_draw_str_aligned( + canvas, 127, 46, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); + furi_string_reset(disp_str); + furi_string_set_str(disp_str, model->state.error); + elements_string_fit_width(canvas, disp_str, canvas_width(canvas)); + canvas_draw_str_aligned( + canvas, 127, 56, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); + furi_string_reset(disp_str); + } else if(state == BadKbStateIdle) { + canvas_draw_icon(canvas, 4, 26, &I_Smile_18x18); + canvas_set_font(canvas, FontBigNumbers); + canvas_draw_str_aligned(canvas, 114, 40, AlignRight, AlignBottom, "0"); + canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); + } else if(state == BadKbStateRunning) { + if(model->anim_frame == 0) { + canvas_draw_icon(canvas, 4, 23, &I_EviSmile1_18x21); + } else { + 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, 40, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); + furi_string_reset(disp_str); + canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); + } else if(state == BadKbStateDone) { + canvas_draw_icon(canvas, 4, 23, &I_EviSmile1_18x21); + canvas_set_font(canvas, FontBigNumbers); + canvas_draw_str_aligned(canvas, 114, 40, AlignRight, AlignBottom, "100"); + furi_string_reset(disp_str); + canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); + } else if(state == BadKbStateDelay) { + if(model->anim_frame == 0) { + canvas_draw_icon(canvas, 4, 23, &I_EviWaiting1_18x21); + } else { + 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, 40, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); + furi_string_reset(disp_str); + 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, 50, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); + furi_string_reset(disp_str); + } else if((state == BadKbStatePaused) || (state == BadKbStateWaitForBtn)) { + if(model->anim_frame == 0) { + canvas_draw_icon(canvas, 4, 23, &I_EviWaiting1_18x21); + } else { + 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, 40, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); + furi_string_reset(disp_str); + canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned(canvas, 127, 50, AlignRight, AlignBottom, "Paused"); + furi_string_reset(disp_str); + } else { + canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18); + } + + furi_string_free(disp_str); +} + +static bool bad_kb_input_callback(InputEvent* event, void* context) { + furi_assert(context); + BadKb* bad_kb = context; + bool consumed = false; + + if(event->type == InputTypeShort) { + if(event->key == InputKeyLeft) { + consumed = true; + furi_assert(bad_kb->callback); + bad_kb->callback(event->key, bad_kb->context); + } else if(event->key == InputKeyOk) { + with_view_model( + bad_kb->view, BadKbModel * model, { model->pause_wait = false; }, true); + consumed = true; + furi_assert(bad_kb->callback); + bad_kb->callback(event->key, bad_kb->context); + } else if(event->key == InputKeyRight) { + with_view_model( + bad_kb->view, + BadKbModel * model, + { + if((model->state.state == BadKbStateRunning) || + (model->state.state == BadKbStateDelay)) { + model->pause_wait = true; + } + }, + true); + consumed = true; + furi_assert(bad_kb->callback); + bad_kb->callback(event->key, bad_kb->context); + } + } + + return consumed; +} + +BadKb* bad_kb_alloc() { + BadKb* bad_kb = malloc(sizeof(BadKb)); + + bad_kb->view = view_alloc(); + view_allocate_model(bad_kb->view, ViewModelTypeLocking, sizeof(BadKbModel)); + view_set_context(bad_kb->view, bad_kb); + view_set_draw_callback(bad_kb->view, bad_kb_draw_callback); + view_set_input_callback(bad_kb->view, bad_kb_input_callback); + + return bad_kb; +} + +void bad_kb_free(BadKb* bad_kb) { + furi_assert(bad_kb); + view_free(bad_kb->view); + free(bad_kb); +} + +View* bad_kb_get_view(BadKb* bad_kb) { + furi_assert(bad_kb); + return bad_kb->view; +} + +void bad_kb_set_button_callback(BadKb* bad_kb, BadKbButtonCallback callback, void* context) { + furi_assert(bad_kb); + furi_assert(callback); + with_view_model( + bad_kb->view, + BadKbModel * model, + { + UNUSED(model); + bad_kb->callback = callback; + bad_kb->context = context; + }, + true); +} + +void bad_kb_set_file_name(BadKb* bad_kb, const char* name) { + furi_assert(name); + with_view_model( + bad_kb->view, BadKbModel * model, { strlcpy(model->file_name, name, MAX_NAME_LEN); }, true); +} + +void bad_kb_set_layout(BadKb* bad_kb, const char* layout) { + furi_assert(layout); + with_view_model( + bad_kb->view, BadKbModel * model, { strlcpy(model->layout, layout, MAX_NAME_LEN); }, true); +} + +void bad_kb_set_state(BadKb* bad_kb, BadKbState* st) { + furi_assert(st); + uint32_t pin = 0; + if(bad_kb->context != NULL) { + BadKbApp* app = bad_kb->context; + if(app->bt != NULL) { + pin = app->bt->pin; + } + } + st->pin = pin; + with_view_model( + bad_kb->view, + BadKbModel * model, + { + memcpy(&(model->state), st, sizeof(BadKbState)); + model->anim_frame ^= 1; + if(model->state.state == BadKbStatePaused) { + model->pause_wait = false; + } + }, + true); +} + +bool bad_kb_is_idle_state(BadKb* bad_kb) { + bool is_idle = false; + with_view_model( + bad_kb->view, + BadKbModel * model, + { + if((model->state.state == BadKbStateIdle) || (model->state.state == BadKbStateDone) || + (model->state.state == BadKbStateNotConnected)) { + is_idle = true; + } + }, + false); + return is_idle; +} diff --git a/applications/external/bad_kb/views/bad_kb_view.h b/applications/external/bad_kb/views/bad_kb_view.h new file mode 100644 index 00000000000..797fafb69e2 --- /dev/null +++ b/applications/external/bad_kb/views/bad_kb_view.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +typedef void (*BadKbButtonCallback)(InputKey key, void* context); + +typedef struct { + View* view; + BadKbButtonCallback callback; + void* context; +} BadKb; + +typedef struct BadKbState BadKbState; + +BadKb* bad_kb_alloc(); + +void bad_kb_free(BadKb* bad_kb); + +View* bad_kb_get_view(BadKb* bad_kb); + +void bad_kb_set_button_callback(BadKb* bad_kb, BadKbButtonCallback callback, void* context); + +void bad_kb_set_file_name(BadKb* bad_kb, const char* name); + +void bad_kb_set_layout(BadKb* bad_kb, const char* layout); + +void bad_kb_set_state(BadKb* bad_kb, BadKbState* st); + +bool bad_kb_is_idle_state(BadKb* bad_kb); diff --git a/applications/settings/bt_settings_app/scenes/bt_settings_scene_forget_dev_confirm.c b/applications/settings/bt_settings_app/scenes/bt_settings_scene_forget_dev_confirm.c index 713936c4ab5..4732425ea8b 100644 --- a/applications/settings/bt_settings_app/scenes/bt_settings_scene_forget_dev_confirm.c +++ b/applications/settings/bt_settings_app/scenes/bt_settings_scene_forget_dev_confirm.c @@ -36,10 +36,12 @@ bool bt_settings_scene_forget_dev_confirm_on_event(void* context, SceneManagerEv // Also remove keys of BadBT, Bluetooth Remote, TOTP Authenticator Storage* storage = furi_record_open(RECORD_STORAGE); storage_simply_remove(storage, EXT_PATH("apps_data/badbt/.badbt.keys")); + storage_simply_remove(storage, EXT_PATH("apps_data/badkb/.badkb.keys")); storage_simply_remove(storage, EXT_PATH("apps_data/hid_ble/.bt_hid.keys")); storage_simply_remove(storage, EXT_PATH("apps_data/authenticator/.bt_hid.keys")); storage_simply_remove(storage, EXT_PATH("authenticator/.bt_hid.keys")); storage_simply_remove(storage, EXT_PATH("badbt/.badbt.keys")); + storage_simply_remove(storage, EXT_PATH("badkb/.badkb.keys")); furi_record_close(RECORD_STORAGE); scene_manager_next_scene(app->scene_manager, BtSettingsAppSceneForgetDevSuccess); diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 579755b14d8..c340a5a61f6 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,36.0,, +Version,+,36.1,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -1539,6 +1539,7 @@ Function,+,furi_hal_uart_tx,void,"FuriHalUartId, uint8_t*, size_t" Function,+,furi_hal_usb_disable,void, Function,+,furi_hal_usb_enable,void, Function,+,furi_hal_usb_get_config,FuriHalUsbInterface*, +Function,+,furi_hal_usb_get_config_context,void*, Function,-,furi_hal_usb_init,void, Function,+,furi_hal_usb_is_locked,_Bool, Function,+,furi_hal_usb_lock,void, diff --git a/firmware/targets/f7/furi_hal/furi_hal_usb.c b/firmware/targets/f7/furi_hal/furi_hal_usb.c index b88168d5d0c..3f9f7cbee37 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_usb.c +++ b/firmware/targets/f7/furi_hal/furi_hal_usb.c @@ -17,6 +17,7 @@ typedef enum { UsbApiEventTypeSetConfig, UsbApiEventTypeGetConfig, + UsbApiEventTypeGetConfigContext, UsbApiEventTypeLock, UsbApiEventTypeUnlock, UsbApiEventTypeIsLocked, @@ -168,6 +169,21 @@ FuriHalUsbInterface* furi_hal_usb_get_config() { return return_data.void_value; } +void* furi_hal_usb_get_config_context() { + UsbApiEventReturnData return_data = { + .void_value = NULL, + }; + + UsbApiEventMessage msg = { + .lock = api_lock_alloc_locked(), + .type = UsbApiEventTypeGetConfigContext, + .return_data = &return_data, + }; + + furi_hal_usb_send_message(&msg); + return return_data.void_value; +} + void furi_hal_usb_lock() { UsbApiEventMessage msg = { .lock = api_lock_alloc_locked(), @@ -411,6 +427,9 @@ static void usb_process_message(UsbApiEventMessage* message) { case UsbApiEventTypeGetConfig: message->return_data->void_value = usb.interface; break; + case UsbApiEventTypeGetConfigContext: + message->return_data->void_value = usb.interface_context; + break; case UsbApiEventTypeLock: FURI_LOG_I(TAG, "Mode lock"); usb.mode_lock = true; diff --git a/firmware/targets/furi_hal_include/furi_hal_usb.h b/firmware/targets/furi_hal_include/furi_hal_usb.h index 8b49f6c6537..2affb3d6d4d 100644 --- a/firmware/targets/furi_hal_include/furi_hal_usb.h +++ b/firmware/targets/furi_hal_include/furi_hal_usb.h @@ -56,6 +56,12 @@ bool furi_hal_usb_set_config(FuriHalUsbInterface* new_if, void* ctx); */ FuriHalUsbInterface* furi_hal_usb_get_config(); +/** Get USB device configuration context + * + * @return current USB device context + */ +void* furi_hal_usb_get_config_context(); + /** Lock USB device mode switch */ void furi_hal_usb_lock(); diff --git a/firmware/targets/furi_hal_include/furi_hal_usb_hid.h b/firmware/targets/furi_hal_include/furi_hal_usb_hid.h index 13e83ef6752..842accc37aa 100644 --- a/firmware/targets/furi_hal_include/furi_hal_usb_hid.h +++ b/firmware/targets/furi_hal_include/furi_hal_usb_hid.h @@ -9,6 +9,11 @@ extern "C" { #endif +#define HID_MANUF_PRODUCT_NAME_LEN 32 + +#define HID_VID_DEFAULT 0x046D +#define HID_PID_DEFAULT 0xC529 + /** Max number of simultaneously pressed keys (keyboard) */ #define HID_KB_MAX_KEYS 6 /** Max number of simultaneously pressed keys (consumer control) */ @@ -163,8 +168,8 @@ static const uint16_t hid_asciimap[] = { typedef struct { uint32_t vid; uint32_t pid; - char manuf[32]; - char product[32]; + char manuf[HID_MANUF_PRODUCT_NAME_LEN]; + char product[HID_MANUF_PRODUCT_NAME_LEN]; } FuriHalUsbHidConfig; typedef void (*HidStateCallback)(bool state, void* context);