diff --git a/applications/main/bad_ble/application.fam b/applications/main/bad_ble/application.fam new file mode 100644 index 000000000..ac4106eac --- /dev/null +++ b/applications/main/bad_ble/application.fam @@ -0,0 +1,18 @@ +App( + appid="bad_ble", + name="Bad BLE", + apptype=FlipperAppType.EXTERNAL, + entry_point="bad_ble_app", + cdefines=["APP_BAD_BLE"], + requires=[ + "gui", + "dialogs", + ], + stack_size=2 * 1024, + # icon="A_BadUsb_14", + order=70, + fap_category="Main", + fap_icon="badusb_10px.png", + fap_icon_assets="images", + fap_libs=["assets"], +) diff --git a/applications/main/bad_ble/bad_ble_app.c b/applications/main/bad_ble/bad_ble_app.c new file mode 100644 index 000000000..ef1a400d6 --- /dev/null +++ b/applications/main/bad_ble/bad_ble_app.c @@ -0,0 +1,195 @@ +#include "bad_ble_app_i.h" +#include "bad_ble_settings_filename.h" +#include +#include +#include +#include + +#include +#include + +#define BAD_BLE_SETTINGS_PATH BAD_BLE_APP_BASE_FOLDER "/" BAD_BLE_SETTINGS_FILE_NAME + +static bool bad_ble_app_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + BadBleApp* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); +} + +static bool bad_ble_app_back_event_callback(void* context) { + furi_assert(context); + BadBleApp* app = context; + return scene_manager_handle_back_event(app->scene_manager); +} + +static void bad_ble_app_tick_event_callback(void* context) { + furi_assert(context); + BadBleApp* app = context; + scene_manager_handle_tick_event(app->scene_manager); +} + +static void bad_ble_load_settings(BadBleApp* app) { + File* settings_file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); + if(storage_file_open(settings_file, BAD_BLE_SETTINGS_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) { + char chr; + while((storage_file_read(settings_file, &chr, 1) == 1) && + !storage_file_eof(settings_file) && !isspace(chr)) { + furi_string_push_back(app->keyboard_layout, chr); + } + } + storage_file_close(settings_file); + storage_file_free(settings_file); +} + +static void bad_ble_save_settings(BadBleApp* app) { + File* settings_file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); + if(storage_file_open(settings_file, BAD_BLE_SETTINGS_PATH, FSAM_WRITE, FSOM_OPEN_ALWAYS)) { + storage_file_write( + settings_file, + furi_string_get_cstr(app->keyboard_layout), + furi_string_size(app->keyboard_layout)); + storage_file_write(settings_file, "\n", 1); + } + storage_file_close(settings_file); + storage_file_free(settings_file); +} + +void bad_ble_set_name(BadBleApp* app, const char* fmt, ...) { + furi_assert(app); + + va_list args; + va_start(args, fmt); + + vsnprintf(app->name, BAD_BLE_ADV_NAME_MAX_LEN, fmt, args); + + va_end(args); +} + +BadBleApp* bad_ble_app_alloc(char* arg) { + BadBleApp* app = malloc(sizeof(BadBleApp)); + + app->bad_ble_script = NULL; + + app->file_path = furi_string_alloc(); + app->keyboard_layout = furi_string_alloc(); + if(arg && strlen(arg)) { + furi_string_set(app->file_path, arg); + } + + bad_ble_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_ble_scene_handlers, app); + + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + view_dispatcher_set_tick_event_callback( + app->view_dispatcher, bad_ble_app_tick_event_callback, 500); + view_dispatcher_set_custom_event_callback( + app->view_dispatcher, bad_ble_app_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, bad_ble_app_back_event_callback); + + + Bt* bt = furi_record_open(RECORD_BT); + app->bt = bt; + const char *adv_name = bt_get_profile_adv_name(bt); + memcpy(app->name, adv_name, BAD_BLE_ADV_NAME_MAX_LEN); + + const uint8_t *mac_addr = bt_get_profile_mac_address(bt); + memcpy(app->mac, mac_addr, BAD_BLE_MAC_ADDRESS_LEN); + + // Custom Widget + app->widget = widget_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, BadBleAppViewError, widget_get_view(app->widget)); + + app->submenu = submenu_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, BadBleAppViewConfig, submenu_get_view(app->submenu)); + + app->bad_ble_view = bad_ble_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, BadBleAppViewWork, bad_ble_get_view(app->bad_ble_view)); + + app->text_input = text_input_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, BadBleAppViewConfigName, text_input_get_view(app->text_input)); + + app->byte_input = byte_input_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, BadBleAppViewConfigMac, byte_input_get_view(app->byte_input)); + + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + if(!furi_string_empty(app->file_path)) { + app->bad_ble_script = bad_ble_script_open(app->file_path, bt); + bad_ble_script_set_keyboard_layout(app->bad_ble_script, app->keyboard_layout); + scene_manager_next_scene(app->scene_manager, BadBleSceneWork); + } else { + furi_string_set(app->file_path, BAD_BLE_APP_BASE_FOLDER); + scene_manager_next_scene(app->scene_manager, BadBleSceneFileSelect); + } + + return app; +} + +void bad_ble_app_free(BadBleApp* app) { + furi_assert(app); + + if(app->bad_ble_script) { + bad_ble_script_close(app->bad_ble_script); + app->bad_ble_script = NULL; + } + + // Views + view_dispatcher_remove_view(app->view_dispatcher, BadBleAppViewWork); + bad_ble_free(app->bad_ble_view); + + // Custom Widget + view_dispatcher_remove_view(app->view_dispatcher, BadBleAppViewError); + widget_free(app->widget); + + // Submenu + view_dispatcher_remove_view(app->view_dispatcher, BadBleAppViewConfig); + submenu_free(app->submenu); + + // Text Input + view_dispatcher_remove_view(app->view_dispatcher, BadBleAppViewConfigName); + text_input_free(app->text_input); + + // Byte Input + view_dispatcher_remove_view(app->view_dispatcher, BadBleAppViewConfigMac); + byte_input_free(app->byte_input); + + // View dispatcher + view_dispatcher_free(app->view_dispatcher); + scene_manager_free(app->scene_manager); + + // Close records + furi_record_close(RECORD_GUI); + furi_record_close(RECORD_NOTIFICATION); + furi_record_close(RECORD_DIALOGS); + furi_record_close(RECORD_BT); + + bad_ble_save_settings(app); + + furi_string_free(app->file_path); + furi_string_free(app->keyboard_layout); + + free(app); +} + +int32_t bad_ble_app(void* p) { + BadBleApp* bad_ble_app = bad_ble_app_alloc((char*)p); + + view_dispatcher_run(bad_ble_app->view_dispatcher); + + bad_ble_app_free(bad_ble_app); + return 0; +} diff --git a/applications/main/bad_ble/bad_ble_app.h b/applications/main/bad_ble/bad_ble_app.h new file mode 100644 index 000000000..35da3ad2c --- /dev/null +++ b/applications/main/bad_ble/bad_ble_app.h @@ -0,0 +1,13 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct BadBleApp BadBleApp; + +void bad_ble_set_name(BadBleApp *app, const char* fmt, ...); + +#ifdef __cplusplus +} +#endif diff --git a/applications/main/bad_ble/bad_ble_app_i.h b/applications/main/bad_ble/bad_ble_app_i.h new file mode 100644 index 000000000..940706c85 --- /dev/null +++ b/applications/main/bad_ble/bad_ble_app_i.h @@ -0,0 +1,64 @@ +#pragma once + +#include "bad_ble_app.h" +#include "scenes/bad_ble_scene.h" +#include "bad_ble_script.h" +#include "bad_ble_custom_event.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "views/bad_ble_view.h" + +#define BAD_BLE_APP_BASE_FOLDER ANY_PATH("BadUsb") +#define BAD_BLE_APP_PATH_LAYOUT_FOLDER BAD_BLE_APP_BASE_FOLDER "/layouts" +#define BAD_BLE_APP_SCRIPT_EXTENSION ".txt" +#define BAD_BLE_APP_LAYOUT_EXTENSION ".kl" + +#define BAD_BLE_MAC_ADDRESS_LEN 6 // need replace with MAC size maccro +#define BAD_BLE_ADV_NAME_MAX_LEN 18 + +typedef enum { + BadBleAppErrorNoFiles, + BadBleAppErrorCloseRpc, +} BadBleAppError; + +struct BadBleApp { + Gui* gui; + ViewDispatcher* view_dispatcher; + SceneManager* scene_manager; + NotificationApp* notifications; + DialogsApp* dialogs; + Widget* widget; + Submenu* submenu; + + TextInput *text_input; + ByteInput *byte_input; + uint8_t mac[BAD_BLE_MAC_ADDRESS_LEN]; + char name[BAD_BLE_ADV_NAME_MAX_LEN + 1]; + + + BadBleAppError error; + FuriString* file_path; + FuriString* keyboard_layout; + BadBle* bad_ble_view; + BadBleScript* bad_ble_script; + + Bt* bt; +}; + +typedef enum { + BadBleAppViewError, + BadBleAppViewWork, + BadBleAppViewConfig, + BadBleAppViewConfigMac, + BadBleAppViewConfigName +} BadBleAppView; \ No newline at end of file diff --git a/applications/main/bad_ble/bad_ble_custom_event.h b/applications/main/bad_ble/bad_ble_custom_event.h new file mode 100644 index 000000000..ba31411d0 --- /dev/null +++ b/applications/main/bad_ble/bad_ble_custom_event.h @@ -0,0 +1,7 @@ + + +typedef enum BadBleCustomEvent { + BadBleAppCustomEventTextEditResult, + BadBleAppCustomEventByteInputDone, + BadBleCustomEventErrorBack +} BadBleCustomEvent; \ No newline at end of file diff --git a/applications/main/bad_ble/bad_ble_script.c b/applications/main/bad_ble/bad_ble_script.c new file mode 100644 index 000000000..5c3e8ce1b --- /dev/null +++ b/applications/main/bad_ble/bad_ble_script.c @@ -0,0 +1,783 @@ +#include +#include +#include +#include +#include +#include +#include +#include "bad_ble_script.h" +#include + +#include + +#define HID_BT_KEYS_STORAGE_PATH EXT_PATH("apps/Tools/.bt_hid.keys") + +#define TAG "BadBle" +#define WORKER_TAG TAG "Worker" +#define FILE_BUFFER_LEN 16 + +#define SCRIPT_STATE_ERROR (-1) +#define SCRIPT_STATE_END (-2) +#define SCRIPT_STATE_NEXT_LINE (-3) + +#define BADBLE_ASCII_TO_KEY(script, x) \ + (((uint8_t)x < 128) ? (script->layout[(uint8_t)x]) : HID_KEYBOARD_NONE) + +typedef enum { + WorkerEvtToggle = (1 << 0), + WorkerEvtEnd = (1 << 1), + WorkerEvtConnect = (1 << 2), + WorkerEvtDisconnect = (1 << 3), +} WorkerEvtFlags; + + +typedef enum { + LevelRssi122_100, + LevelRssi99_80, + LevelRssi79_60, + LevelRssi59_40, + LevelRssi39_0, + LevelRssiNum, +} LevelRssiDelays; + +const uint8_t bt_hid_delays[LevelRssiNum] = { + 45, // LevelRssi122_100 + 41, // LevelRssi99_80 + 37, // LevelRssi79_60 + 33, // LevelRssi59_40 + 30, // LevelRssi39_0 +}; + +struct BadBleScript { + BadBleState st; + FuriString* file_path; + uint32_t defdelay; + uint16_t layout[128]; + FuriThread* thread; + uint8_t file_buf[FILE_BUFFER_LEN + 1]; + uint8_t buf_start; + uint8_t buf_len; + bool file_end; + FuriString* line; + + FuriString* line_prev; + uint32_t repeat_cnt; + + File* debug_file; + Bt* bt; +}; + +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}, + + {"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}, + {"BACKSPACE", HID_KEYPAD_BACKSPACE}, + {"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}, +}; + +static const char ducky_cmd_comment[] = {"REM"}; +static const char ducky_cmd_id[] = {"ID"}; +static const char ducky_cmd_delay[] = {"DELAY "}; +static const char ducky_cmd_string[] = {"STRING "}; +static const char ducky_cmd_defdelay_1[] = {"DEFAULT_DELAY "}; +static const char ducky_cmd_defdelay_2[] = {"DEFAULTDELAY "}; +static const char ducky_cmd_repeat[] = {"REPEAT "}; +static const char ducky_cmd_sysrq[] = {"SYSRQ "}; + +static const char ducky_cmd_altchar[] = {"ALTCHAR "}; +static const char ducky_cmd_altstr_1[] = {"ALTSTRING "}; +static const char ducky_cmd_altstr_2[] = {"ALTCODE "}; + +static const char ducky_cmd_lang[] = {"DUCKY_LANG"}; + +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, +}; + +/** + * @brief Wait until there are enough free slots in the keyboard buffer + * + * @param n_free_chars Number of free slots to wait for (and consider the buffer not full) +*/ +static void bt_hid_hold_while_keyboard_buffer_full(uint8_t n_free_chars, int32_t timeout) { + uint32_t start = furi_get_tick(); + uint32_t timeout_ms = timeout <= -1 ? 0 : timeout; + while(furi_hal_bt_hid_kb_free_slots(n_free_chars) == false) { + furi_delay_ms(100); + + if(timeout != -1 && (furi_get_tick() - start) > timeout_ms) { + break; + } + } +} + +static 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; +} + +static 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; +} + +static bool ducky_is_line_end(const char chr) { + return ((chr == ' ') || (chr == '\0') || (chr == '\r') || (chr == '\n')); +} + +static void ducky_numlock_on() { + if((furi_hal_hid_get_led_state() & HID_KB_LED_NUM) == 0) { + bt_hid_hold_while_keyboard_buffer_full(1, -1); + furi_hal_bt_hid_kb_press(HID_KEYBOARD_LOCK_NUM_LOCK); + FURI_LOG_I(WORKER_TAG, "BT RSSI: %f\r", (double)furi_hal_bt_get_rssi()); + furi_delay_ms(25); + furi_hal_bt_hid_kb_release(HID_KEYBOARD_LOCK_NUM_LOCK); + } +} + +static bool ducky_numpad_press(const char num) { + if((num < '0') || (num > '9')) return false; + + uint16_t key = numpad_keys[num - '0']; + bt_hid_hold_while_keyboard_buffer_full(1, -1); + FURI_LOG_I(WORKER_TAG, "Pressing %c\r\n", num); + + furi_hal_bt_hid_kb_press(key); + FURI_LOG_I(WORKER_TAG, "BT RSSI: %f\r", (double)furi_hal_bt_get_rssi()); + furi_delay_ms(25); + furi_hal_bt_hid_kb_release(key); + + return true; +} + +static bool ducky_altchar(const char* charcode) { + uint8_t i = 0; + bool state = false; + + FURI_LOG_I(WORKER_TAG, "char %s", charcode); + + bt_hid_hold_while_keyboard_buffer_full(1, -1); + furi_hal_bt_hid_kb_press(KEY_MOD_LEFT_ALT); + + while(!ducky_is_line_end(charcode[i])) { + state = ducky_numpad_press(charcode[i]); + if(state == false) break; + i++; + } + + furi_hal_bt_hid_kb_release(KEY_MOD_LEFT_ALT); + return state; +} + +static bool ducky_altstring(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(temp_str); + if(state == false) break; + i++; + } + return state; +} + +static bool ducky_string(BadBleScript* bad_ble, const char* param) { + uint32_t i = 0; + while(param[i] != '\0') { + uint16_t keycode = BADBLE_ASCII_TO_KEY(bad_ble, param[i]); + if(keycode != HID_KEYBOARD_NONE) { + bt_hid_hold_while_keyboard_buffer_full(1, -1); + furi_hal_bt_hid_kb_press(keycode); + FURI_LOG_I(WORKER_TAG, "BT RSSI: %f\r", (double)furi_hal_bt_get_rssi()); + furi_delay_ms(25); + furi_hal_bt_hid_kb_release(keycode); + } + i++; + } + return true; +} + +static uint16_t ducky_get_keycode(BadBleScript* bad_ble, const char* param, bool accept_chars) { + for(size_t i = 0; i < (sizeof(ducky_keys) / sizeof(ducky_keys[0])); i++) { + size_t key_cmd_len = strlen(ducky_keys[i].name); + if((strncmp(param, ducky_keys[i].name, key_cmd_len) == 0) && + (ducky_is_line_end(param[key_cmd_len]))) { + return ducky_keys[i].keycode; + } + } + if((accept_chars) && (strlen(param) > 0)) { + return (BADBLE_ASCII_TO_KEY(bad_ble, param[0]) & 0xFF); + } + return 0; +} + +static int32_t + ducky_parse_line(BadBleScript* bad_ble, FuriString* line, char* error, size_t error_len) { + uint32_t line_len = furi_string_size(line); + const char* line_tmp = furi_string_get_cstr(line); + bool state = false; + + if(line_len == 0) { + return SCRIPT_STATE_NEXT_LINE; // Skip empty lines + } + + FURI_LOG_D(WORKER_TAG, "line:%s", line_tmp); + + // General commands + if(strncmp(line_tmp, ducky_cmd_comment, strlen(ducky_cmd_comment)) == 0) { + // REM - comment line + return (0); + } else if(strncmp(line_tmp, ducky_cmd_id, strlen(ducky_cmd_id)) == 0) { + // ID - executed in ducky_script_preload + return (0); + } else if(strncmp(line_tmp, ducky_cmd_lang, strlen(ducky_cmd_lang)) == 0) { + // DUCKY_LANG - ignore command to retain compatibility with existing scripts + return (0); + } else if(strncmp(line_tmp, ducky_cmd_delay, strlen(ducky_cmd_delay)) == 0) { + // DELAY + line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; + uint32_t delay_val = 0; + state = ducky_get_number(line_tmp, &delay_val); + if((state) && (delay_val > 0)) { + return (int32_t)delay_val; + } + if(error != NULL) { + snprintf(error, error_len, "Invalid number %s", line_tmp); + } + return SCRIPT_STATE_ERROR; + } else if( + (strncmp(line_tmp, ducky_cmd_defdelay_1, strlen(ducky_cmd_defdelay_1)) == 0) || + (strncmp(line_tmp, ducky_cmd_defdelay_2, strlen(ducky_cmd_defdelay_2)) == 0)) { + // DEFAULT_DELAY + line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; + state = ducky_get_number(line_tmp, &bad_ble->defdelay); + if(!state && error != NULL) { + snprintf(error, error_len, "Invalid number %s", line_tmp); + } + return (state) ? (0) : SCRIPT_STATE_ERROR; + } else if(strncmp(line_tmp, ducky_cmd_string, strlen(ducky_cmd_string)) == 0) { + // STRING + line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; + state = ducky_string(bad_ble, line_tmp); + if(!state && error != NULL) { + snprintf(error, error_len, "Invalid string %s", line_tmp); + } + return (state) ? (0) : SCRIPT_STATE_ERROR; + } else if(strncmp(line_tmp, ducky_cmd_altchar, strlen(ducky_cmd_altchar)) == 0) { + // ALTCHAR + line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; + ducky_numlock_on(); + state = ducky_altchar(line_tmp); + if(!state && error != NULL) { + snprintf(error, error_len, "Invalid altchar %s", line_tmp); + } + return (state) ? (0) : SCRIPT_STATE_ERROR; + } else if( + (strncmp(line_tmp, ducky_cmd_altstr_1, strlen(ducky_cmd_altstr_1)) == 0) || + (strncmp(line_tmp, ducky_cmd_altstr_2, strlen(ducky_cmd_altstr_2)) == 0)) { + // ALTSTRING + line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; + ducky_numlock_on(); + state = ducky_altstring(line_tmp); + if(!state && error != NULL) { + snprintf(error, error_len, "Invalid altstring %s", line_tmp); + } + return (state) ? (0) : SCRIPT_STATE_ERROR; + } else if(strncmp(line_tmp, ducky_cmd_repeat, strlen(ducky_cmd_repeat)) == 0) { + // REPEAT + line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; + state = ducky_get_number(line_tmp, &bad_ble->repeat_cnt); + if(!state && error != NULL) { + snprintf(error, error_len, "Invalid number %s", line_tmp); + } + return (state) ? (0) : SCRIPT_STATE_ERROR; + } else if(strncmp(line_tmp, ducky_cmd_sysrq, strlen(ducky_cmd_sysrq)) == 0) { + // SYSRQ + line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; + uint16_t key = ducky_get_keycode(bad_ble, line_tmp, true); + bt_hid_hold_while_keyboard_buffer_full(1, -1); + furi_hal_bt_hid_kb_press(KEY_MOD_LEFT_ALT | HID_KEYBOARD_PRINT_SCREEN); + furi_hal_bt_hid_kb_press(key); + FURI_LOG_I(WORKER_TAG, "BT RSSI: %f\r", (double)furi_hal_bt_get_rssi()); + furi_delay_ms(25); + furi_hal_bt_hid_kb_release(key); + furi_hal_bt_hid_kb_release(KEY_MOD_LEFT_ALT | HID_KEYBOARD_PRINT_SCREEN); + return (0); + } else { + // Special keys + modifiers + uint16_t key = ducky_get_keycode(bad_ble, line_tmp, false); + if(key == HID_KEYBOARD_NONE) { + if(error != NULL) { + snprintf(error, error_len, "No keycode defined for %s", line_tmp); + } + return SCRIPT_STATE_ERROR; + } + if((key & 0xFF00) != 0) { + // It's a modifier key + line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; + key |= ducky_get_keycode(bad_ble, line_tmp, true); + } + FURI_LOG_I(WORKER_TAG, "Special key pressed %x\r\n", key); + furi_hal_bt_hid_kb_press(key); + FURI_LOG_I(WORKER_TAG, "BT RSSI: %f\r", (double)furi_hal_bt_get_rssi()); + furi_delay_ms(25); + furi_hal_bt_hid_kb_release(key); + return (0); + } +} + +static bool ducky_script_preload(BadBleScript* bad_ble, File* script_file) { + uint8_t ret = 0; + uint32_t line_len = 0; + + furi_string_reset(bad_ble->line); + + do { + ret = storage_file_read(script_file, bad_ble->file_buf, FILE_BUFFER_LEN); + for(uint16_t i = 0; i < ret; i++) { + if(bad_ble->file_buf[i] == '\n' && line_len > 0) { + bad_ble->st.line_nb++; + line_len = 0; + } else { + if(bad_ble->st.line_nb == 0) { // Save first line + furi_string_push_back(bad_ble->line, bad_ble->file_buf[i]); + } + line_len++; + } + } + if(storage_file_eof(script_file)) { + if(line_len > 0) { + bad_ble->st.line_nb++; + break; + } + } + } while(ret > 0); + + storage_file_seek(script_file, 0, true); + furi_string_reset(bad_ble->line); + + return true; +} + +static int32_t ducky_script_execute_next(BadBleScript* bad_ble, File* script_file) { + int32_t delay_val = 0; + + if(bad_ble->repeat_cnt > 0) { + bad_ble->repeat_cnt--; + delay_val = ducky_parse_line( + bad_ble, bad_ble->line_prev, bad_ble->st.error, sizeof(bad_ble->st.error)); + if(delay_val == SCRIPT_STATE_NEXT_LINE) { // Empty line + return 0; + } else if(delay_val < 0) { // Script error + bad_ble->st.error_line = bad_ble->st.line_cur - 1; + FURI_LOG_E(WORKER_TAG, "Unknown command at line %u", bad_ble->st.line_cur - 1U); + return SCRIPT_STATE_ERROR; + } else { + return (delay_val + bad_ble->defdelay); + } + } + + furi_string_set(bad_ble->line_prev, bad_ble->line); + furi_string_reset(bad_ble->line); + + while(1) { + if(bad_ble->buf_len == 0) { + bad_ble->buf_len = storage_file_read(script_file, bad_ble->file_buf, FILE_BUFFER_LEN); + if(storage_file_eof(script_file)) { + if((bad_ble->buf_len < FILE_BUFFER_LEN) && (bad_ble->file_end == false)) { + bad_ble->file_buf[bad_ble->buf_len] = '\n'; + bad_ble->buf_len++; + bad_ble->file_end = true; + } + } + + bad_ble->buf_start = 0; + if(bad_ble->buf_len == 0) return SCRIPT_STATE_END; + } + for(uint8_t i = bad_ble->buf_start; i < (bad_ble->buf_start + bad_ble->buf_len); i++) { + if(bad_ble->file_buf[i] == '\n' && furi_string_size(bad_ble->line) > 0) { + bad_ble->st.line_cur++; + bad_ble->buf_len = bad_ble->buf_len + bad_ble->buf_start - (i + 1); + bad_ble->buf_start = i + 1; + furi_string_trim(bad_ble->line); + delay_val = ducky_parse_line( + bad_ble, bad_ble->line, bad_ble->st.error, sizeof(bad_ble->st.error)); + if(delay_val == SCRIPT_STATE_NEXT_LINE) { // Empty line + return 0; + } else if(delay_val < 0) { + bad_ble->st.error_line = bad_ble->st.line_cur; + if(delay_val == SCRIPT_STATE_NEXT_LINE) { + snprintf( + bad_ble->st.error, sizeof(bad_ble->st.error), "Forbidden empty line"); + FURI_LOG_E( + WORKER_TAG, "Forbidden empty line at line %u", bad_ble->st.line_cur); + } else { + FURI_LOG_E(WORKER_TAG, "Unknown command at line %u", bad_ble->st.line_cur); + } + return SCRIPT_STATE_ERROR; + } else { + return (delay_val + bad_ble->defdelay); + } + } else { + furi_string_push_back(bad_ble->line, bad_ble->file_buf[i]); + } + } + bad_ble->buf_len = 0; + if(bad_ble->file_end) return SCRIPT_STATE_END; + } + + return 0; +} + +static void bad_ble_hid_state_callback(BtStatus status, void* context) { + furi_assert(context); + BadBleScript* bad_ble = (BadBleScript*)context; + bool state = (status == BtStatusConnected); + + if(state == true) + furi_thread_flags_set(furi_thread_get_id(bad_ble->thread), WorkerEvtConnect); + else + furi_thread_flags_set(furi_thread_get_id(bad_ble->thread), WorkerEvtDisconnect); +} + +static int32_t bad_ble_worker(void* context) { + BadBleScript* bad_ble = context; + + BadBleWorkerState worker_state = BadBleStateInit; + int32_t delay_val = 0; + + // init ble hid + bt_disconnect(bad_ble->bt); + + // Wait 2nd core to update nvm storage + furi_delay_ms(200); + + bt_keys_storage_set_storage_path(bad_ble->bt, HID_BT_KEYS_STORAGE_PATH); + + bt_set_profile_adv_name(bad_ble->bt, "Keyboard K99"); + + //furi_hal_bt_set_profile_adv_name("Keyboard K99", FuriHalBtProfileHidKeyboard); + + if(!bt_set_profile(bad_ble->bt, BtProfileHidKeyboard)) { + FURI_LOG_E(TAG, "Failed to switch to HID profile"); + return -1; + } + + furi_hal_bt_start_advertising(); + + FURI_LOG_I(WORKER_TAG, "Init"); + File* script_file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); + bad_ble->line = furi_string_alloc(); + bad_ble->line_prev = furi_string_alloc(); + + bt_set_status_changed_callback(bad_ble->bt, bad_ble_hid_state_callback, bad_ble); + while(1) { + if(worker_state == BadBleStateInit) { // State: initialization + if(storage_file_open( + script_file, + furi_string_get_cstr(bad_ble->file_path), + FSAM_READ, + FSOM_OPEN_EXISTING)) { + if((ducky_script_preload(bad_ble, script_file)) && (bad_ble->st.line_nb > 0)) { + worker_state = BadBleStateNotConnected; // Ready to run + } else { + worker_state = BadBleStateScriptError; // Script preload error + } + } else { + FURI_LOG_E(WORKER_TAG, "File open error"); + worker_state = BadBleStateFileError; // File open error + } + bad_ble->st.state = worker_state; + + } else if(worker_state == BadBleStateNotConnected) { // State: ble not connected + uint32_t flags = furi_thread_flags_wait( + WorkerEvtEnd | WorkerEvtConnect | WorkerEvtToggle, + FuriFlagWaitAny, + FuriWaitForever); + furi_check((flags & FuriFlagError) == 0); + if(flags & WorkerEvtEnd) { + break; + } else if(flags & WorkerEvtConnect) { + worker_state = BadBleStateIdle; // Ready to run + } else if(flags & WorkerEvtToggle) { + worker_state = BadBleStateWillRun; // Will run when ble is connected + } + bad_ble->st.state = worker_state; + + } else if(worker_state == BadBleStateIdle) { // State: ready to start + uint32_t flags = furi_thread_flags_wait( + WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect, + FuriFlagWaitAny, + FuriWaitForever); + furi_check((flags & FuriFlagError) == 0); + if(flags & WorkerEvtEnd) { + break; + } else if(flags & WorkerEvtToggle) { // Start executing script + DOLPHIN_DEED(DolphinDeedBadUsbPlayScript); + delay_val = 0; + bad_ble->buf_len = 0; + bad_ble->st.line_cur = 0; + bad_ble->defdelay = 0; + bad_ble->repeat_cnt = 0; + bad_ble->file_end = false; + storage_file_seek(script_file, 0, true); + worker_state = BadBleStateRunning; + } else if(flags & WorkerEvtDisconnect) { + worker_state = BadBleStateNotConnected; // ble disconnected + } + bad_ble->st.state = worker_state; + + } else if(worker_state == BadBleStateWillRun) { // State: start on connection + uint32_t flags = furi_thread_flags_wait( + WorkerEvtEnd | WorkerEvtConnect | WorkerEvtToggle, + FuriFlagWaitAny, + FuriWaitForever); + furi_check((flags & FuriFlagError) == 0); + if(flags & WorkerEvtEnd) { + break; + } else if(flags & WorkerEvtConnect) { // Start executing script + DOLPHIN_DEED(DolphinDeedBadUsbPlayScript); + delay_val = 0; + bad_ble->buf_len = 0; + bad_ble->st.line_cur = 0; + bad_ble->defdelay = 0; + bad_ble->repeat_cnt = 0; + bad_ble->file_end = false; + storage_file_seek(script_file, 0, true); + // extra time for PC to recognize Flipper as keyboard + furi_thread_flags_wait(0, FuriFlagWaitAny, 1500); + worker_state = BadBleStateRunning; + } else if(flags & WorkerEvtToggle) { // Cancel scheduled execution + worker_state = BadBleStateNotConnected; + } + bad_ble->st.state = worker_state; + + } else if(worker_state == BadBleStateRunning) { // State: running + uint16_t delay_cur = (delay_val > 1000) ? (1000) : (delay_val); + uint32_t flags = furi_thread_flags_wait( + WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect, FuriFlagWaitAny, delay_cur); + delay_val -= delay_cur; + if(!(flags & FuriFlagError)) { + if(flags & WorkerEvtEnd) { + break; + } else if(flags & WorkerEvtToggle) { + worker_state = BadBleStateIdle; // Stop executing script + furi_hal_bt_hid_kb_release_all(); + } else if(flags & WorkerEvtDisconnect) { + worker_state = BadBleStateNotConnected; // ble disconnected + furi_hal_bt_hid_kb_release_all(); + } + bad_ble->st.state = worker_state; + continue; + } else if( + (flags == (unsigned)FuriFlagErrorTimeout) || + (flags == (unsigned)FuriFlagErrorResource)) { + if(delay_val > 0) { + bad_ble->st.delay_remain--; + continue; + } + bad_ble->st.state = BadBleStateRunning; + delay_val = ducky_script_execute_next(bad_ble, script_file); + if(delay_val == SCRIPT_STATE_ERROR) { // Script error + delay_val = 0; + worker_state = BadBleStateScriptError; + bad_ble->st.state = worker_state; + } else if(delay_val == SCRIPT_STATE_END) { // End of script + delay_val = 0; + worker_state = BadBleStateIdle; + bad_ble->st.state = BadBleStateDone; + furi_hal_bt_hid_kb_release_all(); + continue; + } else if(delay_val > 1000) { + bad_ble->st.state = BadBleStateDelay; // Show long delays + bad_ble->st.delay_remain = delay_val / 1000; + } + } else { + furi_check((flags & FuriFlagError) == 0); + } + + } else if( + (worker_state == BadBleStateFileError) || + (worker_state == BadBleStateScriptError)) { // State: error + uint32_t flags = furi_thread_flags_wait( + WorkerEvtEnd, FuriFlagWaitAny, FuriWaitForever); // Waiting for exit command + furi_check((flags & FuriFlagError) == 0); + if(flags & WorkerEvtEnd) { + break; + } + } + } + + // release all keys + bt_hid_hold_while_keyboard_buffer_full(6, 3000); + + // stop ble + bt_set_status_changed_callback(bad_ble->bt, NULL, NULL); + + bt_disconnect(bad_ble->bt); + + // Wait 2nd core to update nvm storage + furi_delay_ms(200); + + bt_keys_storage_set_default_path(bad_ble->bt); + + if(!bt_set_profile(bad_ble->bt, BtProfileSerial)) { + FURI_LOG_E(TAG, "Failed to switch to Serial profile"); + } + + storage_file_close(script_file); + storage_file_free(script_file); + furi_string_free(bad_ble->line); + furi_string_free(bad_ble->line_prev); + + FURI_LOG_I(WORKER_TAG, "End"); + + return 0; +} + +static void bad_ble_script_set_default_keyboard_layout(BadBleScript* bad_ble) { + furi_assert(bad_ble); + memset(bad_ble->layout, HID_KEYBOARD_NONE, sizeof(bad_ble->layout)); + memcpy(bad_ble->layout, hid_asciimap, MIN(sizeof(hid_asciimap), sizeof(bad_ble->layout))); +} + +BadBleScript* bad_ble_script_open(FuriString* file_path, Bt* bt) { + furi_assert(file_path); + + BadBleScript* bad_ble = malloc(sizeof(BadBleScript)); + bad_ble->file_path = furi_string_alloc(); + furi_string_set(bad_ble->file_path, file_path); + bad_ble_script_set_default_keyboard_layout(bad_ble); + + bad_ble->st.state = BadBleStateInit; + bad_ble->st.error[0] = '\0'; + + bad_ble->bt = bt; + + bad_ble->thread = furi_thread_alloc_ex("BadBleWorker", 2048, bad_ble_worker, bad_ble); + furi_thread_start(bad_ble->thread); + return bad_ble; +} //-V773 + +void bad_ble_script_close(BadBleScript* bad_ble) { + furi_assert(bad_ble); + furi_record_close(RECORD_STORAGE); + furi_thread_flags_set(furi_thread_get_id(bad_ble->thread), WorkerEvtEnd); + furi_thread_join(bad_ble->thread); + furi_thread_free(bad_ble->thread); + furi_string_free(bad_ble->file_path); + free(bad_ble); +} + +void bad_ble_script_set_keyboard_layout(BadBleScript* bad_ble, FuriString* layout_path) { + furi_assert(bad_ble); + + if((bad_ble->st.state == BadBleStateRunning) || (bad_ble->st.state == BadBleStateDelay)) { + // do not update keyboard layout while a script is running + return; + } + + File* layout_file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); + if(!furi_string_empty(layout_path)) { + if(storage_file_open( + layout_file, furi_string_get_cstr(layout_path), FSAM_READ, FSOM_OPEN_EXISTING)) { + uint16_t layout[128]; + if(storage_file_read(layout_file, layout, sizeof(layout)) == sizeof(layout)) { + memcpy(bad_ble->layout, layout, sizeof(layout)); + } + } + storage_file_close(layout_file); + } else { + bad_ble_script_set_default_keyboard_layout(bad_ble); + } + storage_file_free(layout_file); +} + +void bad_ble_script_toggle(BadBleScript* bad_ble) { + furi_assert(bad_ble); + furi_thread_flags_set(furi_thread_get_id(bad_ble->thread), WorkerEvtToggle); +} + +BadBleState* bad_ble_script_get_state(BadBleScript* bad_ble) { + furi_assert(bad_ble); + return &(bad_ble->st); +} diff --git a/applications/main/bad_ble/bad_ble_script.h b/applications/main/bad_ble/bad_ble_script.h new file mode 100644 index 000000000..f1553e1db --- /dev/null +++ b/applications/main/bad_ble/bad_ble_script.h @@ -0,0 +1,49 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +typedef struct BadBleScript BadBleScript; + +typedef enum { + BadBleStateInit, + BadBleStateNotConnected, + BadBleStateIdle, + BadBleStateWillRun, + BadBleStateRunning, + BadBleStateDelay, + BadBleStateDone, + BadBleStateScriptError, + BadBleStateFileError, +} BadBleWorkerState; + +typedef struct { + BadBleWorkerState state; + uint16_t line_cur; + uint16_t line_nb; + uint32_t delay_remain; + uint16_t error_line; + char error[64]; +} BadBleState; + +BadBleScript* bad_ble_script_open(FuriString* file_path, Bt *bt); + +void bad_ble_script_close(BadBleScript* bad_ble); + +void bad_ble_script_set_keyboard_layout(BadBleScript* bad_ble, FuriString* layout_path); + +void bad_ble_script_start(BadBleScript* bad_ble); + +void bad_ble_script_stop(BadBleScript* bad_ble); + +void bad_ble_script_toggle(BadBleScript* bad_ble); + +BadBleState* bad_ble_script_get_state(BadBleScript* bad_ble); + +#ifdef __cplusplus +} +#endif diff --git a/applications/main/bad_ble/bad_ble_settings_filename.h b/applications/main/bad_ble/bad_ble_settings_filename.h new file mode 100644 index 000000000..73245ad5f --- /dev/null +++ b/applications/main/bad_ble/bad_ble_settings_filename.h @@ -0,0 +1,3 @@ +#pragma once + +#define BAD_BLE_SETTINGS_FILE_NAME ".BadBle.settings" diff --git a/applications/main/bad_ble/badusb_10px.png b/applications/main/bad_ble/badusb_10px.png new file mode 100644 index 000000000..037474aa3 Binary files /dev/null and b/applications/main/bad_ble/badusb_10px.png differ diff --git a/applications/main/bad_ble/images/ActiveConnection_50x64.png b/applications/main/bad_ble/images/ActiveConnection_50x64.png new file mode 100644 index 000000000..1d7686ddd Binary files /dev/null and b/applications/main/bad_ble/images/ActiveConnection_50x64.png differ diff --git a/applications/main/bad_ble/images/Clock_18x18.png b/applications/main/bad_ble/images/Clock_18x18.png new file mode 100644 index 000000000..ab06d008e Binary files /dev/null and b/applications/main/bad_ble/images/Clock_18x18.png differ diff --git a/applications/main/bad_ble/images/Error_18x18.png b/applications/main/bad_ble/images/Error_18x18.png new file mode 100644 index 000000000..16a5a74d9 Binary files /dev/null and b/applications/main/bad_ble/images/Error_18x18.png differ diff --git a/applications/main/bad_ble/images/EviSmile1_18x21.png b/applications/main/bad_ble/images/EviSmile1_18x21.png new file mode 100644 index 000000000..987af3258 Binary files /dev/null and b/applications/main/bad_ble/images/EviSmile1_18x21.png differ diff --git a/applications/main/bad_ble/images/EviSmile2_18x21.png b/applications/main/bad_ble/images/EviSmile2_18x21.png new file mode 100644 index 000000000..7e28c9f01 Binary files /dev/null and b/applications/main/bad_ble/images/EviSmile2_18x21.png differ diff --git a/applications/main/bad_ble/images/EviWaiting1_18x21.png b/applications/main/bad_ble/images/EviWaiting1_18x21.png new file mode 100644 index 000000000..d39d21733 Binary files /dev/null and b/applications/main/bad_ble/images/EviWaiting1_18x21.png differ diff --git a/applications/main/bad_ble/images/EviWaiting2_18x21.png b/applications/main/bad_ble/images/EviWaiting2_18x21.png new file mode 100644 index 000000000..15ca088fd Binary files /dev/null and b/applications/main/bad_ble/images/EviWaiting2_18x21.png differ diff --git a/applications/main/bad_ble/images/Percent_10x14.png b/applications/main/bad_ble/images/Percent_10x14.png new file mode 100644 index 000000000..677911fd4 Binary files /dev/null and b/applications/main/bad_ble/images/Percent_10x14.png differ diff --git a/applications/main/bad_ble/images/SDQuestion_35x43.png b/applications/main/bad_ble/images/SDQuestion_35x43.png new file mode 100644 index 000000000..9b9c9a58e Binary files /dev/null and b/applications/main/bad_ble/images/SDQuestion_35x43.png differ diff --git a/applications/main/bad_ble/images/Smile_18x18.png b/applications/main/bad_ble/images/Smile_18x18.png new file mode 100644 index 000000000..d2aae0dc3 Binary files /dev/null and b/applications/main/bad_ble/images/Smile_18x18.png differ diff --git a/applications/main/bad_ble/images/UsbTree_48x22.png b/applications/main/bad_ble/images/UsbTree_48x22.png new file mode 100644 index 000000000..cc41b5b9a Binary files /dev/null and b/applications/main/bad_ble/images/UsbTree_48x22.png differ diff --git a/applications/main/bad_ble/images/badusb_10px.png b/applications/main/bad_ble/images/badusb_10px.png new file mode 100644 index 000000000..037474aa3 Binary files /dev/null and b/applications/main/bad_ble/images/badusb_10px.png differ diff --git a/applications/main/bad_ble/images/keyboard_10px.png b/applications/main/bad_ble/images/keyboard_10px.png new file mode 100644 index 000000000..74a10e6db Binary files /dev/null and b/applications/main/bad_ble/images/keyboard_10px.png differ diff --git a/applications/main/bad_ble/scenes/bad_ble_scene.c b/applications/main/bad_ble/scenes/bad_ble_scene.c new file mode 100644 index 000000000..351bb1e79 --- /dev/null +++ b/applications/main/bad_ble/scenes/bad_ble_scene.c @@ -0,0 +1,30 @@ +#include "bad_ble_scene.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const bad_ble_scene_on_enter_handlers[])(void*) = { +#include "bad_ble_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_ble_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "bad_ble_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_ble_scene_on_exit_handlers[])(void* context) = { +#include "bad_ble_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers bad_ble_scene_handlers = { + .on_enter_handlers = bad_ble_scene_on_enter_handlers, + .on_event_handlers = bad_ble_scene_on_event_handlers, + .on_exit_handlers = bad_ble_scene_on_exit_handlers, + .scene_num = BadBleSceneNum, +}; diff --git a/applications/main/bad_ble/scenes/bad_ble_scene.h b/applications/main/bad_ble/scenes/bad_ble_scene.h new file mode 100644 index 000000000..25b19fc4b --- /dev/null +++ b/applications/main/bad_ble/scenes/bad_ble_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) BadBleScene##id, +typedef enum { +#include "bad_ble_scene_config.h" + BadBleSceneNum, +} BadBleScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers bad_ble_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "bad_ble_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_ble_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_ble_scene_config.h" +#undef ADD_SCENE diff --git a/applications/main/bad_ble/scenes/bad_ble_scene_config.c b/applications/main/bad_ble/scenes/bad_ble_scene_config.c new file mode 100644 index 000000000..b601a80af --- /dev/null +++ b/applications/main/bad_ble/scenes/bad_ble_scene_config.c @@ -0,0 +1,72 @@ +#include "../bad_ble_app_i.h" +#include "furi_hal_power.h" + +enum SubmenuIndex { + SubmenuIndexKeyboardLayout, + SubmenuIndexAdvertisementName, + SubmenuIndexMacAddress, +}; + +void bad_ble_scene_config_submenu_callback(void* context, uint32_t index) { + BadBleApp* bad_ble = context; + view_dispatcher_send_custom_event(bad_ble->view_dispatcher, index); +} + +void bad_ble_scene_config_on_enter(void* context) { + BadBleApp* bad_ble = context; + Submenu* submenu = bad_ble->submenu; + + submenu_add_item( + submenu, + "Keyboard layout", + SubmenuIndexKeyboardLayout, + bad_ble_scene_config_submenu_callback, + bad_ble); + + submenu_add_item( + submenu, + "Change adv name", + SubmenuIndexAdvertisementName, + bad_ble_scene_config_submenu_callback, + bad_ble); + + submenu_add_item( + submenu, + "Change MAC address", + SubmenuIndexMacAddress, + bad_ble_scene_config_submenu_callback, + bad_ble); + + submenu_set_selected_item( + submenu, scene_manager_get_scene_state(bad_ble->scene_manager, BadBleSceneConfig)); + + view_dispatcher_switch_to_view(bad_ble->view_dispatcher, BadBleAppViewConfig); +} + +bool bad_ble_scene_config_on_event(void* context, SceneManagerEvent event) { + BadBleApp* bad_ble = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_set_scene_state(bad_ble->scene_manager, BadBleSceneConfig, event.event); + consumed = true; + if(event.event == SubmenuIndexKeyboardLayout) { + scene_manager_next_scene(bad_ble->scene_manager, BadBleSceneConfigLayout); + } else if (event.event == SubmenuIndexAdvertisementName) { + scene_manager_next_scene(bad_ble->scene_manager, BadBleSceneConfigName); + } else if (event.event == SubmenuIndexMacAddress) { + scene_manager_next_scene(bad_ble->scene_manager, BadBleSceneConfigMac); + } else { + furi_crash("Unknown key type"); + } + } + + return consumed; +} + +void bad_ble_scene_config_on_exit(void* context) { + BadBleApp* bad_ble = context; + Submenu* submenu = bad_ble->submenu; + + submenu_reset(submenu); +} diff --git a/applications/main/bad_ble/scenes/bad_ble_scene_config.h b/applications/main/bad_ble/scenes/bad_ble_scene_config.h new file mode 100644 index 000000000..73742d9f4 --- /dev/null +++ b/applications/main/bad_ble/scenes/bad_ble_scene_config.h @@ -0,0 +1,7 @@ +ADD_SCENE(bad_ble, file_select, FileSelect) +ADD_SCENE(bad_ble, work, Work) +ADD_SCENE(bad_ble, error, Error) +ADD_SCENE(bad_ble, config, Config) +ADD_SCENE(bad_ble, config_layout, ConfigLayout) +ADD_SCENE(bad_ble, config_name, ConfigName) +ADD_SCENE(bad_ble, config_mac, ConfigMac) diff --git a/applications/main/bad_ble/scenes/bad_ble_scene_config_layout.c b/applications/main/bad_ble/scenes/bad_ble_scene_config_layout.c new file mode 100644 index 000000000..aabb1b900 --- /dev/null +++ b/applications/main/bad_ble/scenes/bad_ble_scene_config_layout.c @@ -0,0 +1,47 @@ +#include "../bad_ble_app_i.h" +#include "furi_hal_power.h" +#include + +static bool bad_ble_layout_select(BadBleApp* bad_ble) { + furi_assert(bad_ble); + + FuriString* predefined_path; + predefined_path = furi_string_alloc(); + if(!furi_string_empty(bad_ble->keyboard_layout)) { + furi_string_set(predefined_path, bad_ble->keyboard_layout); + } else { + furi_string_set(predefined_path, BAD_BLE_APP_PATH_LAYOUT_FOLDER); + } + + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options( + &browser_options, BAD_BLE_APP_LAYOUT_EXTENSION, &I_keyboard_10px); + + // Input events and views are managed by file_browser + bool res = dialog_file_browser_show( + bad_ble->dialogs, bad_ble->keyboard_layout, predefined_path, &browser_options); + + furi_string_free(predefined_path); + return res; +} + +void bad_ble_scene_config_layout_on_enter(void* context) { + BadBleApp* bad_ble = context; + + if(bad_ble_layout_select(bad_ble)) { + bad_ble_script_set_keyboard_layout(bad_ble->bad_ble_script, bad_ble->keyboard_layout); + } + scene_manager_previous_scene(bad_ble->scene_manager); +} + +bool bad_ble_scene_config_layout_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + // BadBleApp* bad_ble = context; + return false; +} + +void bad_ble_scene_config_layout_on_exit(void* context) { + UNUSED(context); + // BadBleApp* bad_ble = context; +} diff --git a/applications/main/bad_ble/scenes/bad_ble_scene_config_mac.c b/applications/main/bad_ble/scenes/bad_ble_scene_config_mac.c new file mode 100644 index 000000000..2b05d7c59 --- /dev/null +++ b/applications/main/bad_ble/scenes/bad_ble_scene_config_mac.c @@ -0,0 +1,47 @@ +#include "../bad_ble_app_i.h" + +#define TAG "BadBleConfigMac" + +void bad_ble_scene_config_mac_byte_input_callback(void* context) { + BadBleApp* bad_ble = context; + + view_dispatcher_send_custom_event(bad_ble->view_dispatcher, BadBleAppCustomEventByteInputDone); +} + +void bad_ble_scene_config_mac_on_enter(void* context) { + BadBleApp* bad_ble = context; + + // Setup view + ByteInput* byte_input = bad_ble->byte_input; + byte_input_set_header_text(byte_input, "Enter new MAC address"); + byte_input_set_result_callback( + byte_input, + bad_ble_scene_config_mac_byte_input_callback, + NULL, + bad_ble, + bad_ble->mac, + GAP_MAC_ADDR_SIZE); + view_dispatcher_switch_to_view(bad_ble->view_dispatcher, BadBleAppViewConfigMac); +} + +bool bad_ble_scene_config_mac_on_event(void* context, SceneManagerEvent event) { + BadBleApp* bad_ble = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == BadBleAppCustomEventByteInputDone) { + bt_set_profile_mac_address(bad_ble->bt, bad_ble->mac); + scene_manager_previous_scene(bad_ble->scene_manager); + consumed = true; + } + } + return consumed; +} + +void bad_ble_scene_config_mac_on_exit(void* context) { + BadBleApp* bad_ble = context; + + // Clear view + byte_input_set_result_callback(bad_ble->byte_input, NULL, NULL, NULL, NULL, 0); + byte_input_set_header_text(bad_ble->byte_input, ""); +} diff --git a/applications/main/bad_ble/scenes/bad_ble_scene_config_name.c b/applications/main/bad_ble/scenes/bad_ble_scene_config_name.c new file mode 100644 index 000000000..9fdeebe1b --- /dev/null +++ b/applications/main/bad_ble/scenes/bad_ble_scene_config_name.c @@ -0,0 +1,47 @@ +#include "../bad_ble_app_i.h" + +static void bad_ble_scene_config_name_text_input_callback(void* context) { + BadBleApp* bad_ble = context; + + view_dispatcher_send_custom_event( + bad_ble->view_dispatcher, BadBleAppCustomEventTextEditResult); +} + +void bad_ble_scene_config_name_on_enter(void* context) { + BadBleApp* bad_ble = context; + TextInput* text_input = bad_ble->text_input; + + text_input_set_header_text(text_input, "Set BLE adv name"); + + text_input_set_result_callback( + text_input, + bad_ble_scene_config_name_text_input_callback, + bad_ble, + bad_ble->name, + BAD_BLE_ADV_NAME_MAX_LEN, + true); + + view_dispatcher_switch_to_view(bad_ble->view_dispatcher, BadBleAppViewConfigName); +} + +bool bad_ble_scene_config_name_on_event(void* context, SceneManagerEvent event) { + BadBleApp* bad_ble = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + if(event.event == BadBleAppCustomEventTextEditResult) { + bt_set_profile_adv_name(bad_ble->bt, bad_ble->name); + } + scene_manager_previous_scene( + bad_ble->scene_manager); + } + return consumed; +} + +void bad_ble_scene_config_name_on_exit(void* context) { + BadBleApp* bad_ble = context; + TextInput* text_input = bad_ble->text_input; + + text_input_reset(text_input); +} diff --git a/applications/main/bad_ble/scenes/bad_ble_scene_error.c b/applications/main/bad_ble/scenes/bad_ble_scene_error.c new file mode 100644 index 000000000..7460c4042 --- /dev/null +++ b/applications/main/bad_ble/scenes/bad_ble_scene_error.c @@ -0,0 +1,79 @@ +#include "../bad_ble_app_i.h" +#include "../../../settings/desktop_settings/desktop_settings_app.h" + +static void + bad_ble_scene_error_event_callback(GuiButtonType result, InputType type, void* context) { + furi_assert(context); + BadBleApp* app = context; + + if((result == GuiButtonTypeLeft) && (type == InputTypeShort)) { + view_dispatcher_send_custom_event(app->view_dispatcher, BadBleCustomEventErrorBack); + } +} + +void bad_ble_scene_error_on_enter(void* context) { + BadBleApp* app = context; + DesktopSettings* settings = malloc(sizeof(DesktopSettings)); + DESKTOP_SETTINGS_LOAD(settings); + + if(app->error == BadBleAppErrorNoFiles) { + widget_add_icon_element(app->widget, 0, 0, &I_SDQuestion_35x43); + widget_add_string_multiline_element( + app->widget, + 81, + 4, + AlignCenter, + AlignTop, + FontSecondary, + "No SD card or\napp data found.\nThis app will not\nwork without\nrequired files."); + widget_add_button_element( + app->widget, GuiButtonTypeLeft, "Back", bad_ble_scene_error_event_callback, app); + } else if(app->error == BadBleAppErrorCloseRpc) { + widget_add_icon_element(app->widget, 78, 0, &I_ActiveConnection_50x64); + if (settings->sfw_mode) { + widget_add_string_multiline_element( + app->widget, 3, 2, AlignLeft, AlignTop, FontPrimary, "Connection\nis active!"); + widget_add_string_multiline_element( + app->widget, + 3, + 30, + AlignLeft, + AlignTop, + FontSecondary, + "Disconnect from\nPC or phone to\nuse this function."); + } + else { + widget_add_string_multiline_element( + app->widget, 3, 2, AlignLeft, AlignTop, FontPrimary, "I am not\na whore!"); + widget_add_string_multiline_element( + app->widget, + 3, + 30, + AlignLeft, + AlignTop, + FontSecondary, + "Pull out from\nPC or phone to\nuse me like this."); + } + } + + view_dispatcher_switch_to_view(app->view_dispatcher, BadBleAppViewError); + free(settings); +} + +bool bad_ble_scene_error_on_event(void* context, SceneManagerEvent event) { + BadBleApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == BadBleCustomEventErrorBack) { + view_dispatcher_stop(app->view_dispatcher); + consumed = true; + } + } + return consumed; +} + +void bad_ble_scene_error_on_exit(void* context) { + BadBleApp* app = context; + widget_reset(app->widget); +} diff --git a/applications/main/bad_ble/scenes/bad_ble_scene_file_select.c b/applications/main/bad_ble/scenes/bad_ble_scene_file_select.c new file mode 100644 index 000000000..d60f6a246 --- /dev/null +++ b/applications/main/bad_ble/scenes/bad_ble_scene_file_select.c @@ -0,0 +1,51 @@ +#include "../bad_ble_app_i.h" +#include "furi_hal_power.h" +#include + +static bool bad_ble_file_select(BadBleApp* bad_ble) { + furi_assert(bad_ble); + + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options( + &browser_options, BAD_BLE_APP_SCRIPT_EXTENSION, &I_badusb_10px); + browser_options.base_path = BAD_BLE_APP_BASE_FOLDER; + browser_options.skip_assets = true; + + // Input events and views are managed by file_browser + bool res = dialog_file_browser_show( + bad_ble->dialogs, bad_ble->file_path, bad_ble->file_path, &browser_options); + + return res; +} + +void bad_ble_scene_file_select_on_enter(void* context) { + BadBleApp* bad_ble = context; + + // furi_hal_ble_disable(); + if(bad_ble->bad_ble_script) { + bad_ble_script_close(bad_ble->bad_ble_script); + bad_ble->bad_ble_script = NULL; + } + + if(bad_ble_file_select(bad_ble)) { + bad_ble->bad_ble_script = bad_ble_script_open(bad_ble->file_path, bad_ble->bt); + bad_ble_script_set_keyboard_layout(bad_ble->bad_ble_script, bad_ble->keyboard_layout); + + scene_manager_next_scene(bad_ble->scene_manager, BadBleSceneWork); + } else { + // furi_hal_ble_enable(); + view_dispatcher_stop(bad_ble->view_dispatcher); + } +} + +bool bad_ble_scene_file_select_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + // BadBleApp* bad_ble = context; + return false; +} + +void bad_ble_scene_file_select_on_exit(void* context) { + UNUSED(context); + // BadBleApp* bad_ble = context; +} \ No newline at end of file diff --git a/applications/main/bad_ble/scenes/bad_ble_scene_work.c b/applications/main/bad_ble/scenes/bad_ble_scene_work.c new file mode 100644 index 000000000..a2c009fc3 --- /dev/null +++ b/applications/main/bad_ble/scenes/bad_ble_scene_work.c @@ -0,0 +1,54 @@ +#include "../bad_ble_script.h" +#include "../bad_ble_app_i.h" +#include "../views/bad_ble_view.h" +#include "furi_hal.h" +#include "toolbox/path.h" + +void bad_ble_scene_work_button_callback(InputKey key, void* context) { + furi_assert(context); + BadBleApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, key); +} + +bool bad_ble_scene_work_on_event(void* context, SceneManagerEvent event) { + BadBleApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == InputKeyLeft) { + scene_manager_next_scene(app->scene_manager, BadBleSceneConfig); + consumed = true; + } else if(event.event == InputKeyOk) { + bad_ble_script_toggle(app->bad_ble_script); + consumed = true; + } + } else if(event.type == SceneManagerEventTypeTick) { + bad_ble_set_state(app->bad_ble_view, bad_ble_script_get_state(app->bad_ble_script)); + } + return consumed; +} + +void bad_ble_scene_work_on_enter(void* context) { + BadBleApp* app = context; + + FuriString* file_name; + file_name = furi_string_alloc(); + path_extract_filename(app->file_path, file_name, true); + bad_ble_set_file_name(app->bad_ble_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_ble_set_layout(app->bad_ble_view, furi_string_get_cstr(layout)); + furi_string_free(layout); + + bad_ble_set_state(app->bad_ble_view, bad_ble_script_get_state(app->bad_ble_script)); + + bad_ble_set_button_callback(app->bad_ble_view, bad_ble_scene_work_button_callback, app); + view_dispatcher_switch_to_view(app->view_dispatcher, BadBleAppViewWork); +} + +void bad_ble_scene_work_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/main/bad_ble/switch_ble.py b/applications/main/bad_ble/switch_ble.py new file mode 100644 index 000000000..32ed65492 --- /dev/null +++ b/applications/main/bad_ble/switch_ble.py @@ -0,0 +1,24 @@ +import os +import re + +def analyze_and_replace(directory): + # Recursively search for .c and .h files in the given directory + for root, dirs, files in os.walk(directory): + for file in files: + if file.endswith(".c") or file.endswith(".h"): + # Read the contents of the file + with open(os.path.join(root, file), "r") as f: + contents = f.read() + + # Replace all occurrences of "BadUsb" and "bad_usb" with "BadBle" and "bad_ble" + contents = contents.replace("usb", "ble") + contents = contents.replace("USB", "BLE") + contents = contents.replace("Usb", "Ble") + + + # Write the modified contents back to the file + with open(os.path.join(root, file), "w") as f: + f.write(contents) + +# Test the function with a sample directory +analyze_and_replace(".") diff --git a/applications/main/bad_ble/views/bad_ble_view.c b/applications/main/bad_ble/views/bad_ble_view.c new file mode 100644 index 000000000..fa5c099a5 --- /dev/null +++ b/applications/main/bad_ble/views/bad_ble_view.c @@ -0,0 +1,237 @@ +#include "bad_ble_view.h" +#include "../bad_ble_script.h" +#include +#include +#include +#include "../../../settings/desktop_settings/desktop_settings_app.h" + +#define MAX_NAME_LEN 64 + +struct BadBle { + View* view; + BadBleButtonCallback callback; + void* context; +}; + +typedef struct { + char file_name[MAX_NAME_LEN]; + char layout[MAX_NAME_LEN]; + BadBleState state; + uint8_t anim_frame; +} BadBleModel; + +static void bad_ble_draw_callback(Canvas* canvas, void* _model) { + BadBleModel* model = _model; + + FuriString* disp_str; + disp_str = furi_string_alloc_set(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)); + DesktopSettings* settings = malloc(sizeof(DesktopSettings)); + DESKTOP_SETTINGS_LOAD(settings); + + if(strlen(model->layout) == 0) { + furi_string_set(disp_str, "(default)"); + } else { + furi_string_reset(disp_str); + furi_string_push_back(disp_str, '('); + for(size_t i = 0; i < strlen(model->layout); i++) + furi_string_push_back(disp_str, model->layout[i]); + furi_string_push_back(disp_str, ')'); + } + elements_string_fit_width(canvas, disp_str, 128 - 2); + canvas_draw_str( + canvas, 2, 8 + canvas_current_font_height(canvas), furi_string_get_cstr(disp_str)); + + furi_string_reset(disp_str); + + canvas_draw_icon(canvas, 22, 24, &I_UsbTree_48x22); + + if((model->state.state == BadBleStateIdle) || (model->state.state == BadBleStateDone) || + (model->state.state == BadBleStateNotConnected)) { + if (settings->sfw_mode) { + elements_button_center(canvas, "Start"); + } + else { + elements_button_center(canvas, "Cum"); + } + } else if((model->state.state == BadBleStateRunning) || (model->state.state == BadBleStateDelay)) { + elements_button_center(canvas, "Stop"); + } else if(model->state.state == BadBleStateWillRun) { + elements_button_center(canvas, "Cancel"); + } + + if((model->state.state == BadBleStateNotConnected) || + (model->state.state == BadBleStateIdle) || (model->state.state == BadBleStateDone)) { + elements_button_left(canvas, "Config"); + } + + if(model->state.state == BadBleStateNotConnected) { + canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18); + canvas_set_font(canvas, FontPrimary); + if (settings->sfw_mode) { + canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Connect me"); + canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "to a computer"); + } + else { + canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Plug me"); + canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "in, Daddy"); + } + } else if(model->state.state == BadBleStateWillRun) { + canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18); + canvas_set_font(canvas, FontPrimary); + if (settings->sfw_mode) { + canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Will run"); + } + else { + canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Will cum"); + } + canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "on connect"); + } else if(model->state.state == BadBleStateFileError) { + 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(model->state.state == BadBleStateScriptError) { + 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); + canvas_draw_str_aligned(canvas, 127, 56, AlignRight, AlignBottom, model->state.error); + } else if(model->state.state == BadBleStateIdle) { + 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(model->state.state == BadBleStateRunning) { + 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(model->state.state == BadBleStateDone) { + 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(model->state.state == BadBleStateDelay) { + 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 { + canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18); + } + + furi_string_free(disp_str); + free(settings); +} + +static bool bad_ble_input_callback(InputEvent* event, void* context) { + furi_assert(context); + BadBle* bad_ble = context; + bool consumed = false; + + if(event->type == InputTypeShort) { + if((event->key == InputKeyLeft) || (event->key == InputKeyOk)) { + consumed = true; + furi_assert(bad_ble->callback); + bad_ble->callback(event->key, bad_ble->context); + } + } + + return consumed; +} + +BadBle* bad_ble_alloc() { + BadBle* bad_ble = malloc(sizeof(BadBle)); + + bad_ble->view = view_alloc(); + view_allocate_model(bad_ble->view, ViewModelTypeLocking, sizeof(BadBleModel)); + view_set_context(bad_ble->view, bad_ble); + view_set_draw_callback(bad_ble->view, bad_ble_draw_callback); + view_set_input_callback(bad_ble->view, bad_ble_input_callback); + + return bad_ble; +} + +void bad_ble_free(BadBle* bad_ble) { + furi_assert(bad_ble); + view_free(bad_ble->view); + free(bad_ble); +} + +View* bad_ble_get_view(BadBle* bad_ble) { + furi_assert(bad_ble); + return bad_ble->view; +} + +void bad_ble_set_button_callback(BadBle* bad_ble, BadBleButtonCallback callback, void* context) { + furi_assert(bad_ble); + furi_assert(callback); + with_view_model( + bad_ble->view, + BadBleModel * model, + { + UNUSED(model); + bad_ble->callback = callback; + bad_ble->context = context; + }, + true); +} + +void bad_ble_set_file_name(BadBle* bad_ble, const char* name) { + furi_assert(name); + with_view_model( + bad_ble->view, + BadBleModel * model, + { strlcpy(model->file_name, name, MAX_NAME_LEN); }, + true); +} + +void bad_ble_set_layout(BadBle* bad_ble, const char* layout) { + furi_assert(layout); + with_view_model( + bad_ble->view, + BadBleModel * model, + { strlcpy(model->layout, layout, MAX_NAME_LEN); }, + true); +} + +void bad_ble_set_state(BadBle* bad_ble, BadBleState* st) { + furi_assert(st); + with_view_model( + bad_ble->view, + BadBleModel * model, + { + memcpy(&(model->state), st, sizeof(BadBleState)); + model->anim_frame ^= 1; + }, + true); +} diff --git a/applications/main/bad_ble/views/bad_ble_view.h b/applications/main/bad_ble/views/bad_ble_view.h new file mode 100644 index 000000000..ccbd1d97b --- /dev/null +++ b/applications/main/bad_ble/views/bad_ble_view.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include "../bad_ble_script.h" + +typedef struct BadBle BadBle; +typedef void (*BadBleButtonCallback)(InputKey key, void* context); + +BadBle* bad_ble_alloc(); + +void bad_ble_free(BadBle* bad_ble); + +View* bad_ble_get_view(BadBle* bad_ble); + +void bad_ble_set_button_callback(BadBle* bad_ble, BadBleButtonCallback callback, void* context); + +void bad_ble_set_file_name(BadBle* bad_ble, const char* name); + +void bad_ble_set_layout(BadBle* bad_ble, const char* layout); + +void bad_ble_set_state(BadBle* bad_ble, BadBleState* st); diff --git a/applications/main/gpio/scenes/gpio_scene_start.c b/applications/main/gpio/scenes/gpio_scene_start.c index 08b77238f..c06d7b5c6 100644 --- a/applications/main/gpio/scenes/gpio_scene_start.c +++ b/applications/main/gpio/scenes/gpio_scene_start.c @@ -1,3 +1,6 @@ +#ifndef __GPIO_SCENE_START_H__ +#define __GPIO_SCENE_START_H__ + #include "../gpio_app_i.h" #include "furi_hal_power.h" #include "furi_hal_usb.h" @@ -35,7 +38,8 @@ static void gpio_scene_start_var_list_enter_callback(void* context, uint32_t ind } } -static void gpio_scene_start_var_list_change_callback(VariableItem* item) { +static void +gpio_scene_start_var_list_change_callback(VariableItem* item) { GpioApp* app = variable_item_get_context(item); uint8_t index = variable_item_get_current_value_index(item); @@ -117,3 +121,5 @@ void gpio_scene_start_on_exit(void* context) { GpioApp* app = context; variable_item_list_reset(app->var_item_list); } + +#endif // __GPIO_SCENE_START_H__ \ No newline at end of file diff --git a/applications/services/bt/bt_service/bt.c b/applications/services/bt/bt_service/bt.c index e341544b3..030844838 100644 --- a/applications/services/bt/bt_service/bt.c +++ b/applications/services/bt/bt_service/bt.c @@ -384,6 +384,64 @@ static void bt_close_connection(Bt* bt) { furi_event_flag_set(bt->api_event, BT_API_UNLOCK_EVENT); } +static void bt_restart(Bt *bt) { + if (bt->profile == BtProfileHidKeyboard) { + furi_hal_bt_change_app(FuriHalBtProfileHidKeyboard, bt_on_gap_event_callback, bt); + } else { + furi_hal_bt_change_app(FuriHalBtProfileSerial, bt_on_gap_event_callback, bt); + } + furi_hal_bt_start_advertising(); +} + +void bt_set_profile_adv_name(Bt *bt, const char* fmt, ...) { + furi_assert(bt); + furi_assert(fmt); + + char name[FURI_HAL_VERSION_DEVICE_NAME_LENGTH]; + va_list args; + va_start(args, fmt); + vsnprintf(name, sizeof(name), fmt, args); + va_end(args); + if (bt->profile == BtProfileHidKeyboard) { + furi_hal_bt_set_profile_adv_name(FuriHalBtProfileHidKeyboard, name); + } else { + furi_hal_bt_set_profile_adv_name(FuriHalBtProfileSerial, name); + } + + bt_restart(bt); +} + +const char *bt_get_profile_adv_name(Bt *bt) { + furi_assert(bt); + if (bt->profile == BtProfileHidKeyboard) { + return furi_hal_bt_get_profile_adv_name(FuriHalBtProfileHidKeyboard); + } else { + return furi_hal_bt_get_profile_adv_name(FuriHalBtProfileSerial); + } +} + +void bt_set_profile_mac_address(Bt *bt, const uint8_t mac[6]) { + furi_assert(bt); + furi_assert(mac); + + if (bt->profile == BtProfileHidKeyboard) { + furi_hal_bt_set_profile_mac_addr(FuriHalBtProfileHidKeyboard, mac); + } else { + furi_hal_bt_set_profile_mac_addr(FuriHalBtProfileSerial, mac); + } + + bt_restart(bt); +} + +const uint8_t *bt_get_profile_mac_address(Bt *bt) { + furi_assert(bt); + if (bt->profile == BtProfileHidKeyboard) { + return furi_hal_bt_get_profile_mac_addr(FuriHalBtProfileHidKeyboard); + } else { + return furi_hal_bt_get_profile_mac_addr(FuriHalBtProfileSerial); + } +} + int32_t bt_srv(void* p) { UNUSED(p); Bt* bt = bt_alloc(); diff --git a/applications/services/bt/bt_service/bt.h b/applications/services/bt/bt_service/bt.h index ca47936db..4320e007e 100644 --- a/applications/services/bt/bt_service/bt.h +++ b/applications/services/bt/bt_service/bt.h @@ -35,6 +35,14 @@ typedef void (*BtStatusChangedCallback)(BtStatus status, void* context); */ bool bt_set_profile(Bt* bt, BtProfile profile); + +void bt_set_profile_adv_name(Bt *bt, const char* fmt, ...); +const char *bt_get_profile_adv_name(Bt *bt); + +void bt_set_profile_mac_address(Bt *bt, const uint8_t mac[6]); +const uint8_t *bt_get_profile_mac_address(Bt *bt); + + /** Disconnect from Central * * @param bt Bt instance diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 877e20b05..7c1a203cc 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,+,12.3,, +Version,v,12.7,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -571,9 +571,13 @@ Function,+,ble_glue_wait_for_c2_start,_Bool,int32_t Function,-,bsearch,void*,"const void*, const void*, size_t, size_t, __compar_fn_t" Function,+,bt_disconnect,void,Bt* Function,+,bt_forget_bonded_devices,void,Bt* +Function,?,bt_get_profile_adv_name,const char*,Bt* +Function,?,bt_get_profile_mac_address,const uint8_t*,Bt* Function,+,bt_keys_storage_set_default_path,void,Bt* Function,+,bt_keys_storage_set_storage_path,void,"Bt*, const char*" -Function,+,bt_set_profile,_Bool,"Bt*, BtProfile" +Function,?,bt_set_profile,_Bool,"Bt*, BtProfile" +Function,?,bt_set_profile_adv_name,void,"Bt*, const char*, ..." +Function,?,bt_set_profile_mac_address,void,"Bt*, const uint8_t[6]" Function,+,bt_set_status_changed_callback,void,"Bt*, BtStatusChangedCallback, void*" Function,+,buffered_file_stream_alloc,Stream*,Storage* Function,+,buffered_file_stream_close,_Bool,Stream* @@ -603,15 +607,7 @@ Function,+,byte_input_set_result_callback,void,"ByteInput*, ByteInputCallback, B Function,-,bzero,void,"void*, size_t" Function,-,calloc,void*,"size_t, size_t" Function,+,canvas_clear,void,Canvas* -<<<<<<< HEAD -Function,+,canvas_commit,void,Canvas* -======= -<<<<<<< HEAD Function,-,canvas_commit,void,Canvas* -======= -Function,+,canvas_commit,void,Canvas* ->>>>>>> b11b9f1b3 (Gui: Direct Draw API (#2215)) ->>>>>>> e04f2b4ce (api_symbols) Function,+,canvas_current_font_height,uint8_t,Canvas* Function,+,canvas_draw_bitmap,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, const uint8_t*" Function,+,canvas_draw_box,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" @@ -640,15 +636,7 @@ Function,+,canvas_glyph_width,uint8_t,"Canvas*, char" Function,+,canvas_height,uint8_t,Canvas* Function,-,canvas_init,Canvas*, Function,+,canvas_invert_color,void,Canvas* -<<<<<<< HEAD -Function,+,canvas_reset,void,Canvas* -======= -<<<<<<< HEAD Function,-,canvas_reset,void,Canvas* -======= -Function,+,canvas_reset,void,Canvas* ->>>>>>> b11b9f1b3 (Gui: Direct Draw API (#2215)) ->>>>>>> e04f2b4ce (api_symbols) Function,+,canvas_set_bitmap_mode,void,"Canvas*, _Bool" Function,+,canvas_set_color,void,"Canvas*, Color" Function,+,canvas_set_font,void,"Canvas*, Font" @@ -1012,12 +1000,15 @@ Function,+,furi_hal_bt_clear_white_list,_Bool, Function,+,furi_hal_bt_dump_state,void,FuriString* Function,+,furi_hal_bt_ensure_c2_mode,_Bool,BleGlueC2Mode Function,+,furi_hal_bt_get_key_storage_buff,void,"uint8_t**, uint16_t*" +Function,?,furi_hal_bt_get_profile_adv_name,const char*,FuriHalBtProfile +Function,?,furi_hal_bt_get_profile_mac_addr,const uint8_t*,FuriHalBtProfile Function,+,furi_hal_bt_get_radio_stack,FuriHalBtStack, Function,+,furi_hal_bt_get_rssi,float, Function,+,furi_hal_bt_get_transmitted_packets,uint32_t, Function,+,furi_hal_bt_hid_consumer_key_press,_Bool,uint16_t Function,+,furi_hal_bt_hid_consumer_key_release,_Bool,uint16_t Function,+,furi_hal_bt_hid_consumer_key_release_all,_Bool, +Function,?,furi_hal_bt_hid_kb_free_slots,_Bool,uint8_t Function,+,furi_hal_bt_hid_kb_press,_Bool,uint16_t Function,+,furi_hal_bt_hid_kb_release,_Bool,uint16_t Function,+,furi_hal_bt_hid_kb_release_all,_Bool, @@ -1044,6 +1035,8 @@ Function,+,furi_hal_bt_serial_start,void, Function,+,furi_hal_bt_serial_stop,void, Function,+,furi_hal_bt_serial_tx,_Bool,"uint8_t*, uint16_t" Function,+,furi_hal_bt_set_key_storage_change_callback,void,"BleGlueKeyStorageChangedCallback, void*" +Function,?,furi_hal_bt_set_profile_adv_name,void,"FuriHalBtProfile, const char[( 1 + 8 + ( 8 + 1 ) )]" +Function,?,furi_hal_bt_set_profile_mac_addr,void,"FuriHalBtProfile, const uint8_t[( 6 )]" Function,+,furi_hal_bt_start_advertising,void, Function,+,furi_hal_bt_start_app,_Bool,"FuriHalBtProfile, GapEventCallback, void*" Function,+,furi_hal_bt_start_packet_rx,void,"uint8_t, uint8_t" diff --git a/firmware/targets/f7/ble_glue/gap.c b/firmware/targets/f7/ble_glue/gap.c index 8ef037d6b..2bfd2ff3a 100644 --- a/firmware/targets/f7/ble_glue/gap.c +++ b/firmware/targets/f7/ble_glue/gap.c @@ -313,7 +313,7 @@ static void gap_init_svc(Gap* gap) { // Initialize GATT interface aci_gatt_init(); // Initialize GAP interface - // Skip fist symbol AD_TYPE_COMPLETE_LOCAL_NAME + // Skip first symbol AD_TYPE_COMPLETE_LOCAL_NAME char* name = gap->service.adv_name + 1; aci_gap_init( GAP_PERIPHERAL_ROLE, @@ -362,7 +362,7 @@ static void gap_init_svc(Gap* gap) { keypress_supported, CFG_ENCRYPTION_KEY_SIZE_MIN, CFG_ENCRYPTION_KEY_SIZE_MAX, - CFG_USED_FIXED_PIN, + CFG_USED_FIXED_PIN, // 0x0 for no pin 0, PUBLIC_ADDR); // Configure whitelist @@ -482,6 +482,10 @@ bool gap_init(GapConfig* config, GapEventCallback on_event_cb, void* context) { gap->advertise_timer = furi_timer_alloc(gap_advetise_timer_callback, FuriTimerTypeOnce, NULL); // Initialization of GATT & GAP layer gap->service.adv_name = config->adv_name; + FURI_LOG_I(TAG, "Advertising name: %s", &(gap->service.adv_name[1])); + FURI_LOG_I(TAG, "MAC @ : %02X:%02X:%02X:%02X:%02X:%02X", + config->mac_address[0], config->mac_address[1], config->mac_address[2], + config->mac_address[3], config->mac_address[4], config->mac_address[5]); gap_init_svc(gap); // Initialization of the BLE Services SVCCTL_Init(); diff --git a/firmware/targets/f7/furi_hal/furi_hal_bt.c b/firmware/targets/f7/furi_hal/furi_hal_bt.c index 4e10b90d7..61939e12d 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_bt.c +++ b/firmware/targets/f7/furi_hal/furi_hal_bt.c @@ -54,27 +54,25 @@ FuriHalBtProfileConfig profile_config[FuriHalBtProfileNumber] = { }, }, }, - [FuriHalBtProfileHidKeyboard] = - { - .start = furi_hal_bt_hid_start, - .stop = furi_hal_bt_hid_stop, - .config = - { - .adv_service_uuid = HUMAN_INTERFACE_DEVICE_SERVICE_UUID, - .appearance_char = GAP_APPEARANCE_KEYBOARD, - .bonding_mode = true, - .pairing_method = GapPairingPinCodeVerifyYesNo, - .mac_address = FURI_HAL_BT_DEFAULT_MAC_ADDR, - .conn_param = - { - .conn_int_min = 0x18, // 30 ms - .conn_int_max = 0x24, // 45 ms - .slave_latency = 0, - .supervisor_timeout = 0, - }, - }, - }, -}; + [FuriHalBtProfileHidKeyboard] = { + .start = furi_hal_bt_hid_start, + .stop = furi_hal_bt_hid_stop, + .config = + { + .adv_service_uuid = HUMAN_INTERFACE_DEVICE_SERVICE_UUID, + .appearance_char = GAP_APPEARANCE_KEYBOARD, + .bonding_mode = true, + .pairing_method = GapPairingPinCodeVerifyYesNo, + .mac_address = FURI_HAL_BT_DEFAULT_MAC_ADDR, + .conn_param = + { + .conn_int_min = 0x18, // 30 ms + .conn_int_max = 0x24, // 45 ms + .slave_latency = 0, + .supervisor_timeout = 0, + }, + }, + }}; FuriHalBtProfileConfig* current_profile = NULL; void furi_hal_bt_init() { @@ -201,26 +199,33 @@ bool furi_hal_bt_start_app(FuriHalBtProfile profile, GapEventCallback event_cb, FURI_LOG_E(TAG, "Can't start Ble App - unsupported radio stack"); break; } - // Set mac address - memcpy( - profile_config[profile].config.mac_address, - furi_hal_version_get_ble_mac(), - sizeof(profile_config[profile].config.mac_address)); - // Set advertise name - strlcpy( - profile_config[profile].config.adv_name, - furi_hal_version_get_ble_local_device_name_ptr(), - FURI_HAL_VERSION_DEVICE_NAME_LENGTH); - // Configure GAP GapConfig* config = &profile_config[profile].config; + if (strlen(&(profile_config[profile].config.adv_name[1])) == 0) { + // Set advertise name + strlcpy( + profile_config[profile].config.adv_name, + furi_hal_version_get_ble_local_device_name_ptr(), + FURI_HAL_VERSION_DEVICE_NAME_LENGTH); + } + // Configure GAP if(profile == FuriHalBtProfileSerial) { + // Set mac address + memcpy( + profile_config[profile].config.mac_address, + furi_hal_version_get_ble_mac(), + sizeof(profile_config[profile].config.mac_address)); config->adv_service_uuid |= furi_hal_version_get_hw_color(); } else if(profile == FuriHalBtProfileHidKeyboard) { // Change MAC address for HID profile - config->mac_address[2]++; - // Change name Flipper -> Flipper Remote - const char* clicker_str = "Flipper Remote"; - memcpy(&config->adv_name[1], clicker_str, strlen(clicker_str)); + uint8_t default_mac[GAP_MAC_ADDR_SIZE] = FURI_HAL_BT_DEFAULT_MAC_ADDR; + if(memcmp(config->mac_address, default_mac, 6) == 0) { + config->mac_address[2]++; + } + // Change name Flipper -> Control + if(strlen(&config->adv_name[1]) == 0) { + const char* clicker_str = "Control"; + memcpy(&config->adv_name[1], clicker_str, strlen(clicker_str)); + } } if(!gap_init(config, event_cb, context)) { gap_thread_stop(); @@ -444,3 +449,32 @@ bool furi_hal_bt_ensure_c2_mode(BleGlueC2Mode mode) { FURI_LOG_E(TAG, "Failed to switch C2 mode: %d", fw_start_res); return false; } + +void furi_hal_bt_set_profile_adv_name(FuriHalBtProfile profile, const char name[FURI_HAL_VERSION_DEVICE_NAME_LENGTH]) { + furi_assert(profile < FuriHalBtProfileNumber); + furi_assert(name); + + memcpy(&(profile_config[profile].config.adv_name[1]), + name, FURI_HAL_VERSION_DEVICE_NAME_LENGTH); + +} + +const char* furi_hal_bt_get_profile_adv_name(FuriHalBtProfile profile) { + furi_assert(profile < FuriHalBtProfileNumber); + return &(profile_config[profile].config.adv_name[1]); +} + +void furi_hal_bt_set_profile_mac_addr( + FuriHalBtProfile profile, + const uint8_t mac_addr[GAP_MAC_ADDR_SIZE]) { + furi_assert(profile < FuriHalBtProfileNumber); + furi_assert(mac_addr); + + memcpy(profile_config[profile].config.mac_address, mac_addr, GAP_MAC_ADDR_SIZE); + +} + +const uint8_t* furi_hal_bt_get_profile_mac_addr(FuriHalBtProfile profile) { + furi_assert(profile < FuriHalBtProfileNumber); + return profile_config[profile].config.mac_address; +} diff --git a/firmware/targets/f7/furi_hal/furi_hal_bt_hid.c b/firmware/targets/f7/furi_hal/furi_hal_bt_hid.c index ab3855f42..501704420 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_bt_hid.c +++ b/firmware/targets/f7/furi_hal/furi_hal_bt_hid.c @@ -138,10 +138,11 @@ void furi_hal_bt_hid_start() { if(!hid_svc_is_started()) { hid_svc_start(); } - // Configure HID Keyboard + kb_report = malloc(sizeof(FuriHalBtHidKbReport)); mouse_report = malloc(sizeof(FuriHalBtHidMouseReport)); consumer_report = malloc(sizeof(FuriHalBtHidConsumerReport)); + // Configure Report Map characteristic hid_svc_update_report_map( furi_hal_bt_hid_report_map_data, sizeof(furi_hal_bt_hid_report_map_data)); @@ -180,17 +181,30 @@ void furi_hal_bt_hid_stop() { bool furi_hal_bt_hid_kb_press(uint16_t button) { furi_assert(kb_report); - for(uint8_t i = 0; i < FURI_HAL_BT_HID_KB_MAX_KEYS; i++) { + uint8_t i; + for(i = 0; i < FURI_HAL_BT_HID_KB_MAX_KEYS; i++) { if(kb_report->key[i] == 0) { kb_report->key[i] = button & 0xFF; break; } } + if(i == FURI_HAL_BT_HID_KB_MAX_KEYS) { + return false; + } kb_report->mods |= (button >> 8); return hid_svc_update_input_report( ReportNumberKeyboard, (uint8_t*)kb_report, sizeof(FuriHalBtHidKbReport)); } +bool furi_hal_bt_hid_kb_free_slots(uint8_t n_empty_slots) { + furi_assert(kb_report); + for(uint8_t i = 0; n_empty_slots > 0 && i < FURI_HAL_BT_HID_KB_MAX_KEYS; i++) { + if(kb_report->key[i] == 0) + n_empty_slots--; + } + return (n_empty_slots == 0); +} + bool furi_hal_bt_hid_kb_release(uint16_t button) { furi_assert(kb_report); for(uint8_t i = 0; i < FURI_HAL_BT_HID_KB_MAX_KEYS; i++) { diff --git a/firmware/targets/furi_hal_include/furi_hal_bt.h b/firmware/targets/furi_hal_include/furi_hal_bt.h index 800fc3fe3..5127972f5 100644 --- a/firmware/targets/furi_hal_include/furi_hal_bt.h +++ b/firmware/targets/furi_hal_include/furi_hal_bt.h @@ -224,6 +224,14 @@ uint32_t furi_hal_bt_get_transmitted_packets(); */ bool furi_hal_bt_ensure_c2_mode(BleGlueC2Mode mode); +void furi_hal_bt_set_profile_adv_name(FuriHalBtProfile profile, const char name[FURI_HAL_VERSION_DEVICE_NAME_LENGTH]); + +const char *furi_hal_bt_get_profile_adv_name(FuriHalBtProfile profile); + +void furi_hal_bt_set_profile_mac_addr(FuriHalBtProfile profile, const uint8_t mac_addr[GAP_MAC_ADDR_SIZE]); + +const uint8_t *furi_hal_bt_get_profile_mac_addr(FuriHalBtProfile profile); + #ifdef __cplusplus } #endif diff --git a/firmware/targets/furi_hal_include/furi_hal_bt_hid.h b/firmware/targets/furi_hal_include/furi_hal_bt_hid.h index 4e74bbda7..e7b40f079 100644 --- a/firmware/targets/furi_hal_include/furi_hal_bt_hid.h +++ b/firmware/targets/furi_hal_include/furi_hal_bt_hid.h @@ -86,6 +86,15 @@ bool furi_hal_bt_hid_consumer_key_release(uint16_t button); */ bool furi_hal_bt_hid_consumer_key_release_all(); +/** + * @brief Check if keyboard buffer has free slots + * + * @param n_emptry_slots number of empty slots in buffer to consider buffer is not full + * + * @return true if there is enough free slots in buffer +*/ +bool furi_hal_bt_hid_kb_free_slots(uint8_t n_empty_slots); + #ifdef __cplusplus } #endif