diff --git a/applications/external/bad_bt/application.fam b/applications/external/bad_bt/application.fam new file mode 100644 index 000000000..ddd8eee19 --- /dev/null +++ b/applications/external/bad_bt/application.fam @@ -0,0 +1,17 @@ +App( + appid="bad_bt", + name="Bad BT", + apptype=FlipperAppType.EXTERNAL, + entry_point="bad_bt_app", + requires=[ + "gui", + "dialogs", + ], + stack_size=2 * 1024, + order=70, + fap_libs=["assets"], + fap_category="Tools", + fap_icon="images/badbt_10px.png", + fap_icon_assets="images", + fap_icon_assets_symbol="bad_bt", +) diff --git a/applications/external/bad_bt/bad_bt_app.c b/applications/external/bad_bt/bad_bt_app.c new file mode 100644 index 000000000..6ac7d4fa4 --- /dev/null +++ b/applications/external/bad_bt/bad_bt_app.c @@ -0,0 +1,333 @@ +#include "bad_bt_app.h" +#include +#include +#include +#include +#include + +#include +#include + +#define BAD_BT_SETTINGS_FILE_NAME ".badbt.settings" +#define BAD_BT_APP_PATH_BOUND_KEYS_FOLDER EXT_PATH("badbt") +#define BAD_BT_APP_PATH_BOUND_KEYS_FILE BAD_BT_APP_PATH_BOUND_KEYS_FOLDER "/.badbt.keys" + +#define BAD_BT_SETTINGS_PATH BAD_BT_APP_BASE_CONFIG_FOLDER "/" BAD_BT_SETTINGS_FILE_NAME + +static bool bad_bt_app_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + BadBtApp* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); +} + +static bool bad_bt_app_back_event_callback(void* context) { + furi_assert(context); + BadBtApp* app = context; + return scene_manager_handle_back_event(app->scene_manager); +} + +static void bad_bt_app_tick_event_callback(void* context) { + furi_assert(context); + BadBtApp* app = context; + scene_manager_handle_tick_event(app->scene_manager); +} + +static void bad_bt_load_settings(BadBtApp* app) { + furi_string_reset(app->keyboard_layout); + strcpy(app->config.bt_name, ""); + memcpy( + app->config.bt_mac, + furi_hal_bt_get_profile_mac_addr(FuriHalBtProfileHidKeyboard), + BAD_BT_MAC_ADDRESS_LEN); + + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* file = flipper_format_file_alloc(storage); + if(flipper_format_file_open_existing(file, BAD_BT_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, "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)) { + strcpy(app->config.bt_name, furi_string_get_cstr(tmp_str)); + } else { + strcpy(app->config.bt_name, ""); + } + if(!flipper_format_read_hex( + file, "Bt_Mac", (uint8_t*)&app->config.bt_mac, BAD_BT_MAC_ADDRESS_LEN)) { + memcpy( + app->config.bt_mac, + furi_hal_bt_get_profile_mac_addr(FuriHalBtProfileHidKeyboard), + BAD_BT_MAC_ADDRESS_LEN); + } + 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_bt_save_settings(BadBtApp* app) { + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* file = flipper_format_file_alloc(storage); + if(flipper_format_file_open_always(file, BAD_BT_SETTINGS_PATH)) { + flipper_format_write_string(file, "Keyboard_Layout", app->keyboard_layout); + flipper_format_write_bool(file, "BT_Remember", &(app->bt_remember), 1); + flipper_format_write_string_cstr(file, "Bt_Name", app->config.bt_name); + flipper_format_write_hex( + file, "Bt_Mac", (uint8_t*)&app->config.bt_mac, BAD_BT_MAC_ADDRESS_LEN); + flipper_format_file_close(file); + } + flipper_format_free(file); + furi_record_close(RECORD_STORAGE); +} + +void bad_bt_reload_worker(BadBtApp* app) { + bad_bt_script_close(app->bad_bt_script); + app->bad_bt_script = bad_bt_script_open(app->file_path, app->bt, app); + bad_bt_script_set_keyboard_layout(app->bad_bt_script, app->keyboard_layout); +} + +void bad_kb_config_refresh_menu(BadBtApp* app) { + scene_manager_next_scene(app->scene_manager, BadBtSceneConfig); + scene_manager_previous_scene(app->scene_manager); +} + +int32_t bad_bt_config_switch_mode(BadBtApp* app) { + bad_bt_reload_worker(app); + furi_hal_bt_start_advertising(); + bad_kb_config_refresh_menu(app); + return 0; +} + +void bad_bt_config_switch_remember_mode(BadBtApp* app) { + if(app->bt_remember) { + furi_hal_bt_set_profile_pairing_method( + FuriHalBtProfileHidKeyboard, GapPairingPinCodeVerifyYesNo); + bt_set_profile_mac_address(app->bt, (uint8_t*)&BAD_BT_BOUND_MAC_ADDRESS); + bt_enable_peer_key_update(app->bt); + } else { + furi_hal_bt_set_profile_pairing_method(FuriHalBtProfileHidKeyboard, GapPairingNone); + bt_set_profile_mac_address(app->bt, app->config.bt_mac); + bt_disable_peer_key_update(app->bt); + } + bad_bt_reload_worker(app); +} + +int32_t bad_bt_connection_init(BadBtApp* app) { + // Set original name and mac address in prev config + strcpy( + app->prev_config.bt_name, furi_hal_bt_get_profile_adv_name(FuriHalBtProfileHidKeyboard)); + + memcpy(app->prev_config.bt_mac, furi_hal_version_get_ble_mac(), BAD_BT_MAC_ADDRESS_LEN); + + bt_timeout = bt_hid_delays[LevelRssi39_0]; + bt_disconnect(app->bt); + // Wait 2nd core to update nvm storage + furi_delay_ms(200); + bt_keys_storage_set_storage_path(app->bt, BAD_BT_APP_PATH_BOUND_KEYS_FILE); + if(strcmp(app->config.bt_name, "") != 0) { + furi_hal_bt_set_profile_adv_name(FuriHalBtProfileHidKeyboard, app->config.bt_name); + } + if(app->bt_remember) { + furi_hal_bt_set_profile_mac_addr( + FuriHalBtProfileHidKeyboard, (uint8_t*)&BAD_BT_BOUND_MAC_ADDRESS); + furi_hal_bt_set_profile_pairing_method( + FuriHalBtProfileHidKeyboard, GapPairingPinCodeVerifyYesNo); + } else { + if(memcmp( + app->config.bt_mac, (uint8_t*)&BAD_BT_EMPTY_MAC_ADDRESS, BAD_BT_MAC_ADDRESS_LEN) != + 0) { + furi_hal_bt_set_profile_mac_addr(FuriHalBtProfileHidKeyboard, app->config.bt_mac); + } + furi_hal_bt_set_profile_pairing_method(FuriHalBtProfileHidKeyboard, GapPairingNone); + } + bt_set_profile(app->bt, BtProfileHidKeyboard); + if(strcmp(app->config.bt_name, "") == 0) { + strcpy(app->config.bt_name, furi_hal_bt_get_profile_adv_name(FuriHalBtProfileHidKeyboard)); + } + if(memcmp(app->config.bt_mac, (uint8_t*)&BAD_BT_EMPTY_MAC_ADDRESS, BAD_BT_MAC_ADDRESS_LEN) == + 0) { + memcpy( + app->config.bt_mac, + furi_hal_bt_get_profile_mac_addr(FuriHalBtProfileHidKeyboard), + BAD_BT_MAC_ADDRESS_LEN); + } + + furi_hal_bt_start_advertising(); + if(app->bt_remember) { + bt_enable_peer_key_update(app->bt); + } else { + bt_disable_peer_key_update(app->bt); + } + + return 0; +} + +void bad_bt_connection_deinit(BadBtApp* app) { + bt_disconnect(app->bt); + // Wait 2nd core to update nvm storage + furi_delay_ms(200); + bt_keys_storage_set_default_path(app->bt); + furi_hal_bt_set_profile_adv_name(FuriHalBtProfileHidKeyboard, app->prev_config.bt_name); + furi_hal_bt_set_profile_mac_addr(FuriHalBtProfileHidKeyboard, app->prev_config.bt_mac); + furi_hal_bt_set_profile_pairing_method( + FuriHalBtProfileHidKeyboard, GapPairingPinCodeVerifyYesNo); + bt_set_profile(app->bt, BtProfileSerial); + bt_enable_peer_key_update(app->bt); +} + +BadBtApp* bad_bt_app_alloc(char* arg) { + BadBtApp* app = malloc(sizeof(BadBtApp)); + + app->bad_bt_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_BT_APP_BASE_CONFIG_FOLDER); + furi_record_close(RECORD_STORAGE); + + bad_bt_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_bt_scene_handlers, app); + + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + view_dispatcher_set_tick_event_callback( + app->view_dispatcher, bad_bt_app_tick_event_callback, 500); + view_dispatcher_set_custom_event_callback( + app->view_dispatcher, bad_bt_app_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, bad_bt_app_back_event_callback); + + Bt* bt = furi_record_open(RECORD_BT); + app->bt = bt; + app->bt->suppress_pin_screen = true; + + // Custom Widget + app->widget = widget_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, BadBtAppViewError, widget_get_view(app->widget)); + + app->var_item_list = variable_item_list_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, BadBtAppViewConfig, variable_item_list_get_view(app->var_item_list)); + + app->bad_bt_view = bad_bt_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, BadBtAppViewWork, bad_bt_get_view(app->bad_bt_view)); + + app->text_input = text_input_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, BadBtAppViewConfigName, text_input_get_view(app->text_input)); + + app->byte_input = byte_input_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, BadBtAppViewConfigMac, byte_input_get_view(app->byte_input)); + + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + app->conn_init_thread = furi_thread_alloc_ex( + "BadBtConnInit", 1024, (FuriThreadCallback)bad_bt_connection_init, app); + furi_thread_start(app->conn_init_thread); + if(!furi_string_empty(app->file_path)) { + app->bad_bt_script = bad_bt_script_open(app->file_path, app->bt, app); + bad_bt_script_set_keyboard_layout(app->bad_bt_script, app->keyboard_layout); + scene_manager_next_scene(app->scene_manager, BadBtSceneWork); + } else { + furi_string_set(app->file_path, BAD_BT_APP_BASE_FOLDER); + scene_manager_next_scene(app->scene_manager, BadBtSceneFileSelect); + } + + return app; +} + +void bad_bt_app_free(BadBtApp* app) { + furi_assert(app); + + if(app->bad_bt_script) { + bad_bt_script_close(app->bad_bt_script); + app->bad_bt_script = NULL; + } + + // Views + view_dispatcher_remove_view(app->view_dispatcher, BadBtAppViewWork); + bad_bt_free(app->bad_bt_view); + + // Custom Widget + view_dispatcher_remove_view(app->view_dispatcher, BadBtAppViewError); + widget_free(app->widget); + + // Variable item list + view_dispatcher_remove_view(app->view_dispatcher, BadBtAppViewConfig); + variable_item_list_free(app->var_item_list); + + // Text Input + view_dispatcher_remove_view(app->view_dispatcher, BadBtAppViewConfigName); + text_input_free(app->text_input); + + // Byte Input + view_dispatcher_remove_view(app->view_dispatcher, BadBtAppViewConfigMac); + byte_input_free(app->byte_input); + + // View dispatcher + view_dispatcher_free(app->view_dispatcher); + scene_manager_free(app->scene_manager); + + // Restore bt 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); + bad_bt_connection_deinit(app); + } + + // Close records + furi_record_close(RECORD_GUI); + furi_record_close(RECORD_NOTIFICATION); + furi_record_close(RECORD_DIALOGS); + furi_record_close(RECORD_BT); + + bad_bt_save_settings(app); + + furi_string_free(app->file_path); + furi_string_free(app->keyboard_layout); + + free(app); +} + +int32_t bad_bt_app(void* p) { + BadBtApp* bad_bt_app = bad_bt_app_alloc((char*)p); + + view_dispatcher_run(bad_bt_app->view_dispatcher); + + bad_bt_app_free(bad_bt_app); + return 0; +} diff --git a/applications/external/bad_bt/bad_bt_app.h b/applications/external/bad_bt/bad_bt_app.h new file mode 100644 index 000000000..13b0844b0 --- /dev/null +++ b/applications/external/bad_bt/bad_bt_app.h @@ -0,0 +1,39 @@ +#pragma once + +#include "scenes/bad_bt_scene.h" +#include "helpers/ducky_script.h" + +#include +#include +#include +#include +#include +#include "bad_bt_icons.h" + +#define BAD_BT_APP_BASE_FOLDER EXT_PATH("badusb") +#define BAD_BT_APP_BASE_CONFIG_FOLDER EXT_PATH("badbt") +#define BAD_BT_APP_PATH_LAYOUT_FOLDER BAD_BT_APP_BASE_FOLDER "/assets/layouts" +#define BAD_BT_APP_SCRIPT_EXTENSION ".txt" +#define BAD_BT_APP_LAYOUT_EXTENSION ".kl" + +typedef enum BadBtCustomEvent { + BadBtAppCustomEventTextEditResult, + BadBtAppCustomEventByteInputDone, + BadBtCustomEventErrorBack +} BadBtCustomEvent; + +typedef enum { + BadBtAppViewError, + BadBtAppViewWork, + BadBtAppViewConfig, + BadBtAppViewConfigMac, + BadBtAppViewConfigName +} BadBtAppView; + +void bad_bt_config_switch_remember_mode(BadBtApp* app); + +int32_t bad_bt_connection_init(BadBtApp* app); + +void bad_bt_connection_deinit(BadBtApp* app); + +void bad_kb_config_refresh_menu(BadBtApp* app); \ No newline at end of file diff --git a/applications/external/bad_bt/helpers/ducky_script.c b/applications/external/bad_bt/helpers/ducky_script.c new file mode 100644 index 000000000..a59d377f2 --- /dev/null +++ b/applications/external/bad_bt/helpers/ducky_script.c @@ -0,0 +1,777 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "ducky_script.h" +#include "ducky_script_i.h" +#include +#include +#include "../bad_bt_app.h" + +const uint8_t BAD_BT_BOUND_MAC_ADDRESS[BAD_BT_MAC_ADDRESS_LEN] = + {0x41, 0x4a, 0xef, 0xb6, 0xa9, 0xd4}; +const uint8_t BAD_BT_EMPTY_MAC_ADDRESS[BAD_BT_MAC_ADDRESS_LEN] = + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +#define TAG "BadBT" +#define WORKER_TAG TAG "Worker" + +#define BADBT_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] = { + 30, // LevelRssi122_100 + 25, // LevelRssi99_80 + 20, // LevelRssi79_60 + 17, // LevelRssi59_40 + 14, // 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 { + WorkerEvtToggle = (1 << 0), + WorkerEvtEnd = (1 << 1), + WorkerEvtConnect = (1 << 2), + WorkerEvtDisconnect = (1 << 3), +} 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(BadBtScript* bad_bt, 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 (BADBT_ASCII_TO_KEY(bad_bt, 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(BadBtScript* bad_bt) { + UNUSED(bad_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); + } +} + +bool ducky_numpad_press(BadBtScript* bad_bt, const char num) { + UNUSED(bad_bt); + if((num < '0') || (num > '9')) return false; + + uint16_t key = numpad_keys[num - '0']; + furi_hal_bt_hid_kb_press(key); + furi_delay_ms(bt_timeout); + furi_hal_bt_hid_kb_release(key); + + return true; +} + +bool ducky_altchar(BadBtScript* bad_bt, const char* charcode) { + uint8_t i = 0; + bool state = false; + + furi_hal_bt_hid_kb_press(KEY_MOD_LEFT_ALT); + + while(!ducky_is_line_end(charcode[i])) { + state = ducky_numpad_press(bad_bt, charcode[i]); + if(state == false) break; + i++; + } + + furi_hal_bt_hid_kb_release(KEY_MOD_LEFT_ALT); + + return state; +} + +bool ducky_altstring(BadBtScript* bad_bt, 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_bt, temp_str); + if(state == false) break; + i++; + } + return state; +} + +int32_t ducky_error(BadBtScript* bad_bt, const char* text, ...) { + va_list args; + va_start(args, text); + + vsnprintf(bad_bt->st.error, sizeof(bad_bt->st.error), text, args); + + va_end(args); + return SCRIPT_STATE_ERROR; +} + +bool ducky_string(BadBtScript* bad_bt, const char* param) { + uint32_t i = 0; + + while(param[i] != '\0') { + if(param[i] != '\n') { + uint16_t keycode = BADBT_ASCII_TO_KEY(bad_bt, param[i]); + if(keycode != HID_KEYBOARD_NONE) { + furi_hal_bt_hid_kb_press(keycode); + furi_delay_ms(bt_timeout); + furi_hal_bt_hid_kb_release(keycode); + } + } else { + furi_hal_bt_hid_kb_press(HID_KEYBOARD_RETURN); + furi_delay_ms(bt_timeout); + furi_hal_bt_hid_kb_release(HID_KEYBOARD_RETURN); + } + i++; + } + bad_bt->stringdelay = 0; + return true; +} + +static bool ducky_string_next(BadBtScript* bad_bt) { + if(bad_bt->string_print_pos >= furi_string_size(bad_bt->string_print)) { + return true; + } + + char print_char = furi_string_get_char(bad_bt->string_print, bad_bt->string_print_pos); + + if(print_char != '\n') { + uint16_t keycode = BADBT_ASCII_TO_KEY(bad_bt, print_char); + if(keycode != HID_KEYBOARD_NONE) { + furi_hal_bt_hid_kb_press(keycode); + furi_delay_ms(bt_timeout); + furi_hal_bt_hid_kb_release(keycode); + } + } else { + furi_hal_bt_hid_kb_press(HID_KEYBOARD_RETURN); + furi_delay_ms(bt_timeout); + furi_hal_bt_hid_kb_release(HID_KEYBOARD_RETURN); + } + + bad_bt->string_print_pos++; + + return false; +} + +static int32_t ducky_parse_line(BadBtScript* bad_bt, 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_bt, line_tmp); + if(cmd_result != SCRIPT_STATE_CMD_UNKNOWN) { + return cmd_result; + } + + // Special keys + modifiers + uint16_t key = ducky_get_keycode(bad_bt, line_tmp, false); + if(key == HID_KEYBOARD_NONE) { + return ducky_error(bad_bt, "No keycode defined for %s", line_tmp); + } + 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_bt, line_tmp, true); + } + furi_hal_bt_hid_kb_press(key); + furi_delay_ms(bt_timeout); + furi_hal_bt_hid_kb_release(key); + + return 0; +} + +static bool ducky_set_bt_id(BadBtScript* bad_bt, const char* line) { + size_t line_len = strlen(line); + size_t mac_len = BAD_BT_MAC_ADDRESS_LEN * 3; + if(line_len < mac_len + 1) return false; // MAC + at least 1 char for name + + uint8_t mac[BAD_BT_MAC_ADDRESS_LEN]; + for(size_t i = 0; i < BAD_BT_MAC_ADDRESS_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, &mac[i])) { + return false; + } + } + + furi_hal_bt_set_profile_adv_name(FuriHalBtProfileHidKeyboard, line + mac_len); + bt_set_profile_mac_address(bad_bt->bt, mac); + return true; +} + +static bool ducky_script_preload(BadBtScript* bad_bt, File* script_file) { + uint8_t ret = 0; + uint32_t line_len = 0; + + furi_string_reset(bad_bt->line); + + do { + ret = storage_file_read(script_file, bad_bt->file_buf, FILE_BUFFER_LEN); + for(uint16_t i = 0; i < ret; i++) { + if(bad_bt->file_buf[i] == '\n' && line_len > 0) { + bad_bt->st.line_nb++; + line_len = 0; + } else { + if(bad_bt->st.line_nb == 0) { // Save first line + furi_string_push_back(bad_bt->line, bad_bt->file_buf[i]); + } + line_len++; + } + } + if(storage_file_eof(script_file)) { + if(line_len > 0) { + bad_bt->st.line_nb++; + break; + } + } + } while(ret > 0); + + const char* line_tmp = furi_string_get_cstr(bad_bt->line); + if(bad_bt->app->switch_mode_thread) { + furi_thread_join(bad_bt->app->switch_mode_thread); + furi_thread_free(bad_bt->app->switch_mode_thread); + bad_bt->app->switch_mode_thread = NULL; + } + // Looking for ID or BT_ID command at first line + bad_bt->set_usb_id = false; + bad_bt->set_bt_id = false; + bad_bt->has_usb_id = strncmp(line_tmp, ducky_cmd_id, strlen(ducky_cmd_id)) == 0; + // TODO: We setting has_usb_id to its value but ignoring it for now and not using anywhere here, may be used in a future to detect script type + bad_bt->has_bt_id = strncmp(line_tmp, ducky_cmd_bt_id, strlen(ducky_cmd_bt_id)) == 0; + if(bad_bt->has_bt_id) { + if(!bad_bt->app->bt_remember) { + bad_bt->set_bt_id = ducky_set_bt_id(bad_bt, &line_tmp[strlen(ducky_cmd_bt_id) + 1]); + } + } + + bad_kb_config_refresh_menu(bad_bt->app); + + if(!bad_bt->set_bt_id) { + const char* bt_name = bad_bt->app->config.bt_name; + const uint8_t* bt_mac = bad_bt->app->bt_remember ? (uint8_t*)&BAD_BT_BOUND_MAC_ADDRESS : + bad_bt->app->config.bt_mac; + bool reset_name = strncmp( + bt_name, + furi_hal_bt_get_profile_adv_name(FuriHalBtProfileHidKeyboard), + BAD_BT_ADV_NAME_MAX_LEN); + bool reset_mac = memcmp( + bt_mac, + furi_hal_bt_get_profile_mac_addr(FuriHalBtProfileHidKeyboard), + BAD_BT_MAC_ADDRESS_LEN); + if(reset_name && reset_mac) { + furi_hal_bt_set_profile_adv_name(FuriHalBtProfileHidKeyboard, bt_name); + } else if(reset_name) { + bt_set_profile_adv_name(bad_bt->bt, bt_name); + } + if(reset_mac) { + bt_set_profile_mac_address(bad_bt->bt, bt_mac); + } + } + + storage_file_seek(script_file, 0, true); + furi_string_reset(bad_bt->line); + + return true; +} + +static int32_t ducky_script_execute_next(BadBtScript* bad_bt, File* script_file) { + int32_t delay_val = 0; + + if(bad_bt->repeat_cnt > 0) { + bad_bt->repeat_cnt--; + delay_val = ducky_parse_line(bad_bt, bad_bt->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_bt->st.error_line = bad_bt->st.line_cur - 1; + FURI_LOG_E(WORKER_TAG, "Unknown command at line %u", bad_bt->st.line_cur - 1U); + return SCRIPT_STATE_ERROR; + } else { + return (delay_val + bad_bt->defdelay); + } + } + + furi_string_set(bad_bt->line_prev, bad_bt->line); + furi_string_reset(bad_bt->line); + + while(1) { + if(bad_bt->buf_len == 0) { + bad_bt->buf_len = storage_file_read(script_file, bad_bt->file_buf, FILE_BUFFER_LEN); + if(storage_file_eof(script_file)) { + if((bad_bt->buf_len < FILE_BUFFER_LEN) && (bad_bt->file_end == false)) { + bad_bt->file_buf[bad_bt->buf_len] = '\n'; + bad_bt->buf_len++; + bad_bt->file_end = true; + } + } + + bad_bt->buf_start = 0; + if(bad_bt->buf_len == 0) return SCRIPT_STATE_END; + } + for(uint8_t i = bad_bt->buf_start; i < (bad_bt->buf_start + bad_bt->buf_len); i++) { + if(bad_bt->file_buf[i] == '\n' && furi_string_size(bad_bt->line) > 0) { + bad_bt->st.line_cur++; + bad_bt->buf_len = bad_bt->buf_len + bad_bt->buf_start - (i + 1); + bad_bt->buf_start = i + 1; + furi_string_trim(bad_bt->line); + delay_val = ducky_parse_line(bad_bt, bad_bt->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_bt->st.error_line = bad_bt->st.line_cur; + FURI_LOG_E(WORKER_TAG, "Unknown command at line %u", bad_bt->st.line_cur); + return SCRIPT_STATE_ERROR; + } else { + return (delay_val + bad_bt->defdelay); + } + } else { + furi_string_push_back(bad_bt->line, bad_bt->file_buf[i]); + } + } + bad_bt->buf_len = 0; + if(bad_bt->file_end) return SCRIPT_STATE_END; + } + + return 0; +} + +static void bad_bt_bt_hid_state_callback(BtStatus status, void* context) { + furi_assert(context); + BadBtScript* bad_bt = context; + bool state = (status == BtStatusConnected); + + if(state == true) { + LevelRssiRange r = bt_remote_rssi_range(bad_bt->bt); + if(r != LevelRssiError) { + bt_timeout = bt_hid_delays[r]; + } + furi_thread_flags_set(furi_thread_get_id(bad_bt->thread), WorkerEvtConnect); + } else { + furi_thread_flags_set(furi_thread_get_id(bad_bt->thread), WorkerEvtDisconnect); + } +} + +static uint32_t bad_bt_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; +} + +static int32_t bad_bt_worker(void* context) { + BadBtScript* bad_bt = context; + + BadBtWorkerState worker_state = BadBtStateInit; + int32_t delay_val = 0; + + FURI_LOG_I(WORKER_TAG, "Init"); + File* script_file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); + bad_bt->line = furi_string_alloc(); + bad_bt->line_prev = furi_string_alloc(); + bad_bt->string_print = furi_string_alloc(); + + bt_set_status_changed_callback(bad_bt->bt, bad_bt_bt_hid_state_callback, bad_bt); + + while(1) { + if(worker_state == BadBtStateInit) { // State: initialization + if(storage_file_open( + script_file, + furi_string_get_cstr(bad_bt->file_path), + FSAM_READ, + FSOM_OPEN_EXISTING)) { + if((ducky_script_preload(bad_bt, script_file)) && (bad_bt->st.line_nb > 0)) { + if(furi_hal_bt_is_connected()) { + worker_state = BadBtStateIdle; // Ready to run + } else { + worker_state = BadBtStateNotConnected; // Not connected + } + + } else { + worker_state = BadBtStateScriptError; // Script preload error + } + } else { + FURI_LOG_E(WORKER_TAG, "File open error"); + worker_state = BadBtStateFileError; // File open error + } + bad_bt->st.state = worker_state; + + } else if(worker_state == BadBtStateNotConnected) { // State: Not connected + uint32_t flags = bad_bt_flags_get( + WorkerEvtEnd | WorkerEvtConnect | WorkerEvtToggle, FuriWaitForever); + + if(flags & WorkerEvtEnd) { + break; + } else if(flags & WorkerEvtConnect) { + worker_state = BadBtStateIdle; // Ready to run + } else if(flags & WorkerEvtToggle) { + worker_state = BadBtStateWillRun; // Will run when connected + } + bad_bt->st.state = worker_state; + + } else if(worker_state == BadBtStateIdle) { // State: ready to start + uint32_t flags = bad_bt_flags_get( + WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect, FuriWaitForever); + + if(flags & WorkerEvtEnd) { + break; + } else if(flags & WorkerEvtToggle) { // Start executing script + delay_val = 0; + bad_bt->buf_len = 0; + bad_bt->st.line_cur = 0; + bad_bt->defdelay = 0; + bad_bt->stringdelay = 0; + bad_bt->repeat_cnt = 0; + bad_bt->key_hold_nb = 0; + bad_bt->file_end = false; + storage_file_seek(script_file, 0, true); + bad_bt_script_set_keyboard_layout(bad_bt, bad_bt->keyboard_layout); + worker_state = BadBtStateRunning; + } else if(flags & WorkerEvtDisconnect) { + worker_state = BadBtStateNotConnected; // Disconnected + } + bad_bt->st.state = worker_state; + + } else if(worker_state == BadBtStateWillRun) { // State: start on connection + uint32_t flags = bad_bt_flags_get( + WorkerEvtEnd | WorkerEvtConnect | WorkerEvtToggle, FuriWaitForever); + + if(flags & WorkerEvtEnd) { + break; + } else if(flags & WorkerEvtConnect) { // Start executing script + delay_val = 0; + bad_bt->buf_len = 0; + bad_bt->st.line_cur = 0; + bad_bt->defdelay = 0; + bad_bt->stringdelay = 0; + bad_bt->repeat_cnt = 0; + bad_bt->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 | WorkerEvtToggle, + FuriFlagWaitAny | FuriFlagNoClear, + 1500); + if(flags == (unsigned)FuriFlagErrorTimeout) { + // If nothing happened - start script execution + worker_state = BadBtStateRunning; + } else if(flags & WorkerEvtToggle) { + worker_state = BadBtStateIdle; + furi_thread_flags_clear(WorkerEvtToggle); + } + + update_bt_timeout(bad_bt->bt); + + bad_bt_script_set_keyboard_layout(bad_bt, bad_bt->keyboard_layout); + } else if(flags & WorkerEvtToggle) { // Cancel scheduled execution + worker_state = BadBtStateNotConnected; + } + bad_bt->st.state = worker_state; + + } else if(worker_state == BadBtStateRunning) { // 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 = BadBtStateIdle; // Stop executing script + + furi_hal_bt_hid_kb_release_all(); + + } else if(flags & WorkerEvtDisconnect) { + worker_state = BadBtStateNotConnected; // Disconnected + + furi_hal_bt_hid_kb_release_all(); + } + bad_bt->st.state = worker_state; + continue; + } else if( + (flags == (unsigned)FuriFlagErrorTimeout) || + (flags == (unsigned)FuriFlagErrorResource)) { + if(delay_val > 0) { + bad_bt->st.delay_remain--; + continue; + } + bad_bt->st.state = BadBtStateRunning; + delay_val = ducky_script_execute_next(bad_bt, script_file); + if(delay_val == SCRIPT_STATE_ERROR) { // Script error + delay_val = 0; + worker_state = BadBtStateScriptError; + bad_bt->st.state = worker_state; + + furi_hal_bt_hid_kb_release_all(); + + } else if(delay_val == SCRIPT_STATE_END) { // End of script + delay_val = 0; + worker_state = BadBtStateIdle; + bad_bt->st.state = BadBtStateDone; + + furi_hal_bt_hid_kb_release_all(); + + continue; + } else if(delay_val == SCRIPT_STATE_STRING_START) { // Start printing string with delays + delay_val = bad_bt->defdelay; + bad_bt->string_print_pos = 0; + worker_state = BadBtStateStringDelay; + } else if(delay_val == SCRIPT_STATE_WAIT_FOR_BTN) { // set state to wait for user input + worker_state = BadBtStateWaitForBtn; + bad_bt->st.state = BadBtStateWaitForBtn; // Show long delays + } else if(delay_val > 1000) { + bad_bt->st.state = BadBtStateDelay; // Show long delays + bad_bt->st.delay_remain = delay_val / 1000; + } + } else { + furi_check((flags & FuriFlagError) == 0); + } + } else if(worker_state == BadBtStateWaitForBtn) { // State: Wait for button Press + uint16_t delay_cur = (delay_val > 1000) ? (1000) : (delay_val); + uint32_t flags = furi_thread_flags_wait( + WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect, FuriFlagWaitAny, delay_cur); + if(!(flags & FuriFlagError)) { + if(flags & WorkerEvtEnd) { + break; + } else if(flags & WorkerEvtToggle) { + delay_val = 0; + worker_state = BadBtStateRunning; + } else if(flags & WorkerEvtDisconnect) { + worker_state = BadBtStateNotConnected; // Disconnected + furi_hal_hid_kb_release_all(); + } + bad_bt->st.state = worker_state; + continue; + } + } else if(worker_state == BadBtStateStringDelay) { // State: print string with delays + uint32_t flags = furi_thread_flags_wait( + WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect, + FuriFlagWaitAny, + bad_bt->stringdelay); + + if(!(flags & FuriFlagError)) { + if(flags & WorkerEvtEnd) { + break; + } else if(flags & WorkerEvtToggle) { + worker_state = BadBtStateIdle; // Stop executing script + + furi_hal_bt_hid_kb_release_all(); + + } else if(flags & WorkerEvtDisconnect) { + worker_state = BadBtStateNotConnected; // Disconnected + + furi_hal_bt_hid_kb_release_all(); + } + bad_bt->st.state = worker_state; + continue; + } else if( + (flags == (unsigned)FuriFlagErrorTimeout) || + (flags == (unsigned)FuriFlagErrorResource)) { + bool string_end = ducky_string_next(bad_bt); + if(string_end) { + bad_bt->stringdelay = 0; + worker_state = BadBtStateRunning; + } + } else { + furi_check((flags & FuriFlagError) == 0); + } + } else if( + (worker_state == BadBtStateFileError) || + (worker_state == BadBtStateScriptError)) { // State: error + uint32_t flags = + bad_bt_flags_get(WorkerEvtEnd, FuriWaitForever); // Waiting for exit command + + if(flags & WorkerEvtEnd) { + break; + } + } + + update_bt_timeout(bad_bt->bt); + } + + bt_set_status_changed_callback(bad_bt->bt, NULL, NULL); + + storage_file_close(script_file); + storage_file_free(script_file); + furi_string_free(bad_bt->line); + furi_string_free(bad_bt->line_prev); + furi_string_free(bad_bt->string_print); + + FURI_LOG_I(WORKER_TAG, "End"); + + return 0; +} + +static void bad_bt_script_set_default_keyboard_layout(BadBtScript* bad_bt) { + furi_assert(bad_bt); + furi_string_set_str(bad_bt->keyboard_layout, ""); + memset(bad_bt->layout, HID_KEYBOARD_NONE, sizeof(bad_bt->layout)); + memcpy(bad_bt->layout, hid_asciimap, MIN(sizeof(hid_asciimap), sizeof(bad_bt->layout))); +} + +BadBtScript* bad_bt_script_open(FuriString* file_path, Bt* bt, BadBtApp* app) { + furi_assert(file_path); + + BadBtScript* bad_bt = malloc(sizeof(BadBtScript)); + bad_bt->app = app; + bad_bt->file_path = furi_string_alloc(); + furi_string_set(bad_bt->file_path, file_path); + bad_bt->keyboard_layout = furi_string_alloc(); + bad_bt_script_set_default_keyboard_layout(bad_bt); + + bad_bt->st.state = BadBtStateInit; + bad_bt->st.error[0] = '\0'; + + bad_bt->bt = bt; + + bad_bt->thread = furi_thread_alloc_ex("BadBtWorker", 2048, bad_bt_worker, bad_bt); + furi_thread_start(bad_bt->thread); + return bad_bt; +} + +void bad_bt_script_close(BadBtScript* bad_bt) { + furi_assert(bad_bt); + furi_record_close(RECORD_STORAGE); + furi_thread_flags_set(furi_thread_get_id(bad_bt->thread), WorkerEvtEnd); + furi_thread_join(bad_bt->thread); + furi_thread_free(bad_bt->thread); + furi_string_free(bad_bt->file_path); + furi_string_free(bad_bt->keyboard_layout); + free(bad_bt); +} + +void bad_bt_script_set_keyboard_layout(BadBtScript* bad_bt, FuriString* layout_path) { + furi_assert(bad_bt); + + if((bad_bt->st.state == BadBtStateRunning) || (bad_bt->st.state == BadBtStateDelay)) { + // 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_bt->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_bt->layout, layout, sizeof(layout)); + } + } + storage_file_close(layout_file); + } else { + bad_bt_script_set_default_keyboard_layout(bad_bt); + } + storage_file_free(layout_file); +} + +void bad_bt_script_toggle(BadBtScript* bad_bt) { + furi_assert(bad_bt); + furi_thread_flags_set(furi_thread_get_id(bad_bt->thread), WorkerEvtToggle); +} + +BadBtState* bad_bt_script_get_state(BadBtScript* bad_bt) { + furi_assert(bad_bt); + return &(bad_bt->st); +} \ No newline at end of file diff --git a/applications/external/bad_bt/helpers/ducky_script.h b/applications/external/bad_bt/helpers/ducky_script.h new file mode 100644 index 000000000..ea0f91040 --- /dev/null +++ b/applications/external/bad_bt/helpers/ducky_script.h @@ -0,0 +1,154 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +#include +#include +#include +#include +#include +#include "../views/bad_bt_view.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 { + BadBtStateInit, + BadBtStateNotConnected, + BadBtStateIdle, + BadBtStateWillRun, + BadBtStateRunning, + BadBtStateDelay, + BadBtStateStringDelay, + BadBtStateWaitForBtn, + BadBtStateDone, + BadBtStateScriptError, + BadBtStateFileError, +} BadBtWorkerState; + +struct BadBtState { + BadBtWorkerState state; + uint32_t pin; + uint16_t line_cur; + uint16_t line_nb; + uint32_t delay_remain; + uint16_t error_line; + char error[64]; +}; + +typedef struct BadBtApp BadBtApp; + +typedef struct { + FuriHalUsbHidConfig hid_cfg; + FuriThread* thread; + BadBtState 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; + + bool set_usb_id; + bool set_bt_id; + bool has_usb_id; + bool has_bt_id; + + FuriString* string_print; + size_t string_print_pos; + + Bt* bt; + BadBtApp* app; +} BadBtScript; + +BadBtScript* bad_bt_script_open(FuriString* file_path, Bt* bt, BadBtApp* app); + +void bad_bt_script_close(BadBtScript* bad_bt); + +void bad_bt_script_set_keyboard_layout(BadBtScript* bad_bt, FuriString* layout_path); + +void bad_bt_script_start(BadBtScript* bad_bt); + +void bad_bt_script_stop(BadBtScript* bad_bt); + +void bad_bt_script_toggle(BadBtScript* bad_bt); + +BadBtState* bad_bt_script_get_state(BadBtScript* bad_bt); + +#define BAD_BT_ADV_NAME_MAX_LEN FURI_HAL_BT_ADV_NAME_LENGTH +#define BAD_BT_MAC_ADDRESS_LEN GAP_MAC_ADDR_SIZE + +// this is the MAC address used when we do not forget paired device (BOUND STATE) +extern const uint8_t BAD_BT_BOUND_MAC_ADDRESS[BAD_BT_MAC_ADDRESS_LEN]; +extern const uint8_t BAD_BT_EMPTY_MAC_ADDRESS[BAD_BT_MAC_ADDRESS_LEN]; + +typedef enum { + BadBtAppErrorNoFiles, + BadBtAppErrorCloseRpc, +} BadBtAppError; + +typedef struct { + char bt_name[BAD_BT_ADV_NAME_MAX_LEN]; + uint8_t bt_mac[BAD_BT_MAC_ADDRESS_LEN]; + GapPairing bt_mode; +} BadBtConfig; + +struct BadBtApp { + Gui* gui; + ViewDispatcher* view_dispatcher; + SceneManager* scene_manager; + NotificationApp* notifications; + DialogsApp* dialogs; + Widget* widget; + VariableItemList* var_item_list; + TextInput* text_input; + ByteInput* byte_input; + + BadBtAppError error; + FuriString* file_path; + FuriString* keyboard_layout; + BadBt* bad_bt_view; + BadBtScript* bad_bt_script; + + Bt* bt; + bool bt_remember; + BadBtConfig config; + BadBtConfig prev_config; + FuriThread* conn_init_thread; + FuriThread* switch_mode_thread; +}; + +int32_t bad_bt_config_switch_mode(BadBtApp* app); + +#ifdef __cplusplus +} +#endif diff --git a/applications/external/bad_bt/helpers/ducky_script_commands.c b/applications/external/bad_bt/helpers/ducky_script_commands.c new file mode 100644 index 000000000..eddab96ac --- /dev/null +++ b/applications/external/bad_bt/helpers/ducky_script_commands.c @@ -0,0 +1,201 @@ +#include +#include +#include "ducky_script.h" +#include "ducky_script_i.h" + +typedef int32_t (*DuckyCmdCallback)(BadBtScript* bad_bt, const char* line, int32_t param); + +typedef struct { + char* name; + DuckyCmdCallback callback; + int32_t param; +} DuckyCmd; + +static int32_t ducky_fnc_delay(BadBtScript* bad_bt, 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_bt, "Invalid number %s", line); +} + +static int32_t ducky_fnc_defdelay(BadBtScript* bad_bt, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + bool state = ducky_get_number(line, &bad_bt->defdelay); + if(!state) { + return ducky_error(bad_bt, "Invalid number %s", line); + } + return 0; +} + +static int32_t ducky_fnc_strdelay(BadBtScript* bad_bt, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + bool state = ducky_get_number(line, &bad_bt->stringdelay); + if(!state) { + return ducky_error(bad_bt, "Invalid number %s", line); + } + return 0; +} + +static int32_t ducky_fnc_string(BadBtScript* bad_bt, const char* line, int32_t param) { + line = &line[ducky_get_command_len(line) + 1]; + furi_string_set_str(bad_bt->string_print, line); + if(param == 1) { + furi_string_cat(bad_bt->string_print, "\n"); + } + + if(bad_bt->stringdelay == 0) { // stringdelay not set - run command immidiately + bool state = ducky_string(bad_bt, furi_string_get_cstr(bad_bt->string_print)); + if(!state) { + return ducky_error(bad_bt, "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(BadBtScript* bad_bt, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + bool state = ducky_get_number(line, &bad_bt->repeat_cnt); + if((!state) || (bad_bt->repeat_cnt == 0)) { + return ducky_error(bad_bt, "Invalid number %s", line); + } + return 0; +} + +static int32_t ducky_fnc_sysrq(BadBtScript* bad_bt, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + uint16_t key = ducky_get_keycode(bad_bt, line, true); + + 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(key); + furi_hal_bt_hid_kb_release(KEY_MOD_LEFT_ALT | HID_KEYBOARD_PRINT_SCREEN); + return 0; +} + +static int32_t ducky_fnc_altchar(BadBtScript* bad_bt, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + ducky_numlock_on(bad_bt); + bool state = ducky_altchar(bad_bt, line); + if(!state) { + return ducky_error(bad_bt, "Invalid altchar %s", line); + } + return 0; +} + +static int32_t ducky_fnc_altstring(BadBtScript* bad_bt, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + ducky_numlock_on(bad_bt); + bool state = ducky_altstring(bad_bt, line); + if(!state) { + return ducky_error(bad_bt, "Invalid altstring %s", line); + } + return 0; +} + +static int32_t ducky_fnc_hold(BadBtScript* bad_bt, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + uint16_t key = ducky_get_keycode(bad_bt, line, true); + if(key == HID_KEYBOARD_NONE) { + return ducky_error(bad_bt, "No keycode defined for %s", line); + } + bad_bt->key_hold_nb++; + if(bad_bt->key_hold_nb > (HID_KB_MAX_KEYS - 1)) { + return ducky_error(bad_bt, "Too many keys are hold"); + } + furi_hal_bt_hid_kb_press(key); + + return 0; +} + +static int32_t ducky_fnc_release(BadBtScript* bad_bt, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + uint16_t key = ducky_get_keycode(bad_bt, line, true); + if(key == HID_KEYBOARD_NONE) { + return ducky_error(bad_bt, "No keycode defined for %s", line); + } + if(bad_bt->key_hold_nb == 0) { + return ducky_error(bad_bt, "No keys are hold"); + } + bad_bt->key_hold_nb--; + furi_hal_bt_hid_kb_release(key); + return 0; +} + +static int32_t ducky_fnc_waitforbutton(BadBtScript* bad_bt, const char* line, int32_t param) { + UNUSED(param); + UNUSED(bad_bt); + 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 "BadBT" +#define WORKER_TAG TAG "Worker" + +int32_t ducky_execute_cmd(BadBtScript* bad_bt, 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_bt, line, ducky_commands[i].param)); + } + } + } + + return SCRIPT_STATE_CMD_UNKNOWN; +} diff --git a/applications/external/bad_bt/helpers/ducky_script_i.h b/applications/external/bad_bt/helpers/ducky_script_i.h new file mode 100644 index 000000000..08afa65a4 --- /dev/null +++ b/applications/external/bad_bt/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(BadBtScript* bad_bt, 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(BadBtScript* bad_bt); + +bool ducky_numpad_press(BadBtScript* bad_bt, const char num); + +bool ducky_altchar(BadBtScript* bad_bt, const char* charcode); + +bool ducky_altstring(BadBtScript* bad_bt, const char* param); + +bool ducky_string(BadBtScript* bad_bt, const char* param); + +int32_t ducky_execute_cmd(BadBtScript* bad_bt, const char* line); + +int32_t ducky_error(BadBtScript* bad_bt, const char* text, ...); + +#ifdef __cplusplus +} +#endif diff --git a/applications/external/bad_bt/helpers/ducky_script_keycodes.c b/applications/external/bad_bt/helpers/ducky_script_keycodes.c new file mode 100644 index 000000000..55c52810f --- /dev/null +++ b/applications/external/bad_bt/helpers/ducky_script_keycodes.c @@ -0,0 +1,78 @@ +#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_bt/images/badbt_10px.png b/applications/external/bad_bt/images/badbt_10px.png new file mode 100644 index 000000000..037474aa3 Binary files /dev/null and b/applications/external/bad_bt/images/badbt_10px.png differ diff --git a/applications/external/bad_bt/scenes/bad_bt_scene.c b/applications/external/bad_bt/scenes/bad_bt_scene.c new file mode 100644 index 000000000..c207ae44b --- /dev/null +++ b/applications/external/bad_bt/scenes/bad_bt_scene.c @@ -0,0 +1,30 @@ +#include "bad_bt_scene.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const bad_bt_scene_on_enter_handlers[])(void*) = { +#include "bad_bt_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_bt_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "bad_bt_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_bt_scene_on_exit_handlers[])(void* context) = { +#include "bad_bt_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers bad_bt_scene_handlers = { + .on_enter_handlers = bad_bt_scene_on_enter_handlers, + .on_event_handlers = bad_bt_scene_on_event_handlers, + .on_exit_handlers = bad_bt_scene_on_exit_handlers, + .scene_num = BadBtSceneNum, +}; diff --git a/applications/external/bad_bt/scenes/bad_bt_scene.h b/applications/external/bad_bt/scenes/bad_bt_scene.h new file mode 100644 index 000000000..a316034ef --- /dev/null +++ b/applications/external/bad_bt/scenes/bad_bt_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) BadBtScene##id, +typedef enum { +#include "bad_bt_scene_config.h" + BadBtSceneNum, +} BadBtScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers bad_bt_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "bad_bt_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_bt_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_bt_scene_config.h" +#undef ADD_SCENE diff --git a/applications/external/bad_bt/scenes/bad_bt_scene_config.c b/applications/external/bad_bt/scenes/bad_bt_scene_config.c new file mode 100644 index 000000000..5fc9c0012 --- /dev/null +++ b/applications/external/bad_bt/scenes/bad_bt_scene_config.c @@ -0,0 +1,104 @@ +#include "../bad_bt_app.h" +#include "../helpers/ducky_script.h" +#include "furi_hal_power.h" + +enum VarItemListIndex { + VarItemListIndexKeyboardLayout, + VarItemListIndexBtRemember, + VarItemListIndexBtDeviceName, + VarItemListIndexBtMacAddress, + VarItemListIndexRandomizeBtMac, +}; + +void bad_bt_scene_config_bt_remember_callback(VariableItem* item) { + BadBtApp* bad_bt = variable_item_get_context(item); + bad_bt->bt_remember = variable_item_get_current_value_index(item); + variable_item_set_current_value_text(item, bad_bt->bt_remember ? "ON" : "OFF"); + view_dispatcher_send_custom_event(bad_bt->view_dispatcher, VarItemListIndexBtRemember); +} + +void bad_bt_scene_config_var_item_list_callback(void* context, uint32_t index) { + BadBtApp* bad_bt = context; + view_dispatcher_send_custom_event(bad_bt->view_dispatcher, index); +} + +void bad_bt_scene_config_on_enter(void* context) { + BadBtApp* bad_bt = context; + VariableItemList* var_item_list = bad_bt->var_item_list; + VariableItem* item; + + item = variable_item_list_add(var_item_list, "Keyboard layout", 0, NULL, bad_bt); + + item = variable_item_list_add( + var_item_list, "BT Remember", 2, bad_bt_scene_config_bt_remember_callback, bad_bt); + variable_item_set_current_value_index(item, bad_bt->bt_remember); + variable_item_set_current_value_text(item, bad_bt->bt_remember ? "ON" : "OFF"); + + item = variable_item_list_add(var_item_list, "BT Device Name", 0, NULL, bad_bt); + if(bad_bt->bad_bt_script->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_bt); + if(bad_bt->bt_remember) { + variable_item_set_locked(item, true, "Remember\nmust be Off!"); + } else if(bad_bt->bad_bt_script->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_bt); + if(bad_bt->bt_remember) { + variable_item_set_locked(item, true, "Remember\nmust be Off!"); + } else if(bad_bt->bad_bt_script->set_bt_id) { + variable_item_set_locked(item, true, "Script has\nBT_ID cmd!\nLocked to\nset MAC!"); + } + + variable_item_list_set_enter_callback( + var_item_list, bad_bt_scene_config_var_item_list_callback, bad_bt); + + variable_item_list_set_selected_item( + var_item_list, scene_manager_get_scene_state(bad_bt->scene_manager, BadBtSceneConfig)); + + view_dispatcher_switch_to_view(bad_bt->view_dispatcher, BadBtAppViewConfig); +} + +bool bad_bt_scene_config_on_event(void* context, SceneManagerEvent event) { + BadBtApp* bad_bt = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_set_scene_state(bad_bt->scene_manager, BadBtSceneConfig, event.event); + consumed = true; + switch(event.event) { + case VarItemListIndexKeyboardLayout: + scene_manager_next_scene(bad_bt->scene_manager, BadBtSceneConfigLayout); + break; + case VarItemListIndexBtRemember: + bad_bt_config_switch_remember_mode(bad_bt); + scene_manager_previous_scene(bad_bt->scene_manager); + scene_manager_next_scene(bad_bt->scene_manager, BadBtSceneConfig); + break; + case VarItemListIndexBtDeviceName: + scene_manager_next_scene(bad_bt->scene_manager, BadBtSceneConfigName); + break; + case VarItemListIndexBtMacAddress: + scene_manager_next_scene(bad_bt->scene_manager, BadBtSceneConfigMac); + break; + case VarItemListIndexRandomizeBtMac: + furi_hal_random_fill_buf(bad_bt->config.bt_mac, BAD_BT_MAC_ADDRESS_LEN); + bt_set_profile_mac_address(bad_bt->bt, bad_bt->config.bt_mac); + break; + default: + break; + } + } + + return consumed; +} + +void bad_bt_scene_config_on_exit(void* context) { + BadBtApp* bad_bt = context; + VariableItemList* var_item_list = bad_bt->var_item_list; + + variable_item_list_reset(var_item_list); +} diff --git a/applications/external/bad_bt/scenes/bad_bt_scene_config.h b/applications/external/bad_bt/scenes/bad_bt_scene_config.h new file mode 100644 index 000000000..f7914e6dd --- /dev/null +++ b/applications/external/bad_bt/scenes/bad_bt_scene_config.h @@ -0,0 +1,7 @@ +ADD_SCENE(bad_bt, file_select, FileSelect) +ADD_SCENE(bad_bt, work, Work) +ADD_SCENE(bad_bt, error, Error) +ADD_SCENE(bad_bt, config, Config) +ADD_SCENE(bad_bt, config_layout, ConfigLayout) +ADD_SCENE(bad_bt, config_name, ConfigName) +ADD_SCENE(bad_bt, config_mac, ConfigMac) diff --git a/applications/external/bad_bt/scenes/bad_bt_scene_config_layout.c b/applications/external/bad_bt/scenes/bad_bt_scene_config_layout.c new file mode 100644 index 000000000..b0ce2d084 --- /dev/null +++ b/applications/external/bad_bt/scenes/bad_bt_scene_config_layout.c @@ -0,0 +1,47 @@ +#include "../bad_bt_app.h" +#include "furi_hal_power.h" +#include + +static bool bad_bt_layout_select(BadBtApp* bad_bt) { + furi_assert(bad_bt); + + FuriString* predefined_path; + predefined_path = furi_string_alloc(); + if(!furi_string_empty(bad_bt->keyboard_layout)) { + furi_string_set(predefined_path, bad_bt->keyboard_layout); + } else { + furi_string_set(predefined_path, BAD_BT_APP_PATH_LAYOUT_FOLDER); + } + + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options( + &browser_options, BAD_BT_APP_LAYOUT_EXTENSION, &I_keyboard_10px); + browser_options.base_path = BAD_BT_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_bt->dialogs, bad_bt->keyboard_layout, predefined_path, &browser_options); + + furi_string_free(predefined_path); + return res; +} + +void bad_bt_scene_config_layout_on_enter(void* context) { + BadBtApp* bad_bt = context; + + if(bad_bt_layout_select(bad_bt)) { + bad_bt_script_set_keyboard_layout(bad_bt->bad_bt_script, bad_bt->keyboard_layout); + } + scene_manager_previous_scene(bad_bt->scene_manager); +} + +bool bad_bt_scene_config_layout_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + return false; +} + +void bad_bt_scene_config_layout_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/external/bad_bt/scenes/bad_bt_scene_config_mac.c b/applications/external/bad_bt/scenes/bad_bt_scene_config_mac.c new file mode 100644 index 000000000..47f63e08c --- /dev/null +++ b/applications/external/bad_bt/scenes/bad_bt_scene_config_mac.c @@ -0,0 +1,47 @@ +#include "../bad_bt_app.h" + +#define TAG "BadBtConfigMac" + +void bad_bt_scene_config_mac_byte_input_callback(void* context) { + BadBtApp* bad_bt = context; + + view_dispatcher_send_custom_event(bad_bt->view_dispatcher, BadBtAppCustomEventByteInputDone); +} + +void bad_bt_scene_config_mac_on_enter(void* context) { + BadBtApp* bad_bt = context; + + // Setup view + ByteInput* byte_input = bad_bt->byte_input; + byte_input_set_header_text(byte_input, "Set BT MAC address"); + byte_input_set_result_callback( + byte_input, + bad_bt_scene_config_mac_byte_input_callback, + NULL, + bad_bt, + bad_bt->config.bt_mac, + GAP_MAC_ADDR_SIZE); + view_dispatcher_switch_to_view(bad_bt->view_dispatcher, BadBtAppViewConfigMac); +} + +bool bad_bt_scene_config_mac_on_event(void* context, SceneManagerEvent event) { + BadBtApp* bad_bt = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == BadBtAppCustomEventByteInputDone) { + bt_set_profile_mac_address(bad_bt->bt, bad_bt->config.bt_mac); + scene_manager_previous_scene(bad_bt->scene_manager); + consumed = true; + } + } + return consumed; +} + +void bad_bt_scene_config_mac_on_exit(void* context) { + BadBtApp* bad_bt = context; + + // Clear view + byte_input_set_result_callback(bad_bt->byte_input, NULL, NULL, NULL, NULL, 0); + byte_input_set_header_text(bad_bt->byte_input, ""); +} diff --git a/applications/external/bad_bt/scenes/bad_bt_scene_config_name.c b/applications/external/bad_bt/scenes/bad_bt_scene_config_name.c new file mode 100644 index 000000000..61d198b8c --- /dev/null +++ b/applications/external/bad_bt/scenes/bad_bt_scene_config_name.c @@ -0,0 +1,45 @@ +#include "../bad_bt_app.h" + +static void bad_bt_scene_config_name_text_input_callback(void* context) { + BadBtApp* bad_bt = context; + + view_dispatcher_send_custom_event(bad_bt->view_dispatcher, BadBtAppCustomEventTextEditResult); +} + +void bad_bt_scene_config_name_on_enter(void* context) { + BadBtApp* bad_bt = context; + TextInput* text_input = bad_bt->text_input; + + text_input_set_header_text(text_input, "Set BT device name"); + + text_input_set_result_callback( + text_input, + bad_bt_scene_config_name_text_input_callback, + bad_bt, + bad_bt->config.bt_name, + BAD_BT_ADV_NAME_MAX_LEN, + true); + + view_dispatcher_switch_to_view(bad_bt->view_dispatcher, BadBtAppViewConfigName); +} + +bool bad_bt_scene_config_name_on_event(void* context, SceneManagerEvent event) { + BadBtApp* bad_bt = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + if(event.event == BadBtAppCustomEventTextEditResult) { + bt_set_profile_adv_name(bad_bt->bt, bad_bt->config.bt_name); + } + scene_manager_previous_scene(bad_bt->scene_manager); + } + return consumed; +} + +void bad_bt_scene_config_name_on_exit(void* context) { + BadBtApp* bad_bt = context; + TextInput* text_input = bad_bt->text_input; + + text_input_reset(text_input); +} diff --git a/applications/external/bad_bt/scenes/bad_bt_scene_error.c b/applications/external/bad_bt/scenes/bad_bt_scene_error.c new file mode 100644 index 000000000..e25703e7d --- /dev/null +++ b/applications/external/bad_bt/scenes/bad_bt_scene_error.c @@ -0,0 +1,61 @@ +#include "../bad_bt_app.h" + +static void + bad_bt_scene_error_event_callback(GuiButtonType result, InputType type, void* context) { + furi_assert(context); + BadBtApp* app = context; + + if((result == GuiButtonTypeLeft) && (type == InputTypeShort)) { + view_dispatcher_send_custom_event(app->view_dispatcher, BadBtCustomEventErrorBack); + } +} + +void bad_bt_scene_error_on_enter(void* context) { + BadBtApp* app = context; + + if(app->error == BadBtAppErrorNoFiles) { + 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_bt_scene_error_event_callback, app); + } else if(app->error == BadBtAppErrorCloseRpc) { + widget_add_icon_element(app->widget, 78, 0, &I_ActiveConnection_50x64); + 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."); + } + + view_dispatcher_switch_to_view(app->view_dispatcher, BadBtAppViewError); +} + +bool bad_bt_scene_error_on_event(void* context, SceneManagerEvent event) { + BadBtApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == BadBtCustomEventErrorBack) { + view_dispatcher_stop(app->view_dispatcher); + consumed = true; + } + } + return consumed; +} + +void bad_bt_scene_error_on_exit(void* context) { + BadBtApp* app = context; + widget_reset(app->widget); +} 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 new file mode 100644 index 000000000..b86dc6d71 --- /dev/null +++ b/applications/external/bad_bt/scenes/bad_bt_scene_file_select.c @@ -0,0 +1,49 @@ +#include "../bad_bt_app.h" +#include +#include + +static bool bad_bt_file_select(BadBtApp* bad_bt) { + furi_assert(bad_bt); + + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options( + &browser_options, BAD_BT_APP_SCRIPT_EXTENSION, &I_badbt_10px); + browser_options.base_path = BAD_BT_APP_BASE_FOLDER; + browser_options.skip_assets = true; + + // Input events and views are managed by file_browser + bool res = dialog_file_browser_show( + bad_bt->dialogs, bad_bt->file_path, bad_bt->file_path, &browser_options); + + return res; +} + +void bad_bt_scene_file_select_on_enter(void* context) { + BadBtApp* bad_bt = context; + + if(bad_bt->bad_bt_script) { + bad_bt_script_close(bad_bt->bad_bt_script); + bad_bt->bad_bt_script = NULL; + } + + if(bad_bt_file_select(bad_bt)) { + bad_bt->bad_bt_script = bad_bt_script_open(bad_bt->file_path, bad_bt->bt, bad_bt); + bad_bt_script_set_keyboard_layout(bad_bt->bad_bt_script, bad_bt->keyboard_layout); + + scene_manager_next_scene(bad_bt->scene_manager, BadBtSceneWork); + } else { + view_dispatcher_stop(bad_bt->view_dispatcher); + } +} + +bool bad_bt_scene_file_select_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + // BadBtApp* bad_bt = context; + return false; +} + +void bad_bt_scene_file_select_on_exit(void* context) { + UNUSED(context); + // BadBtApp* bad_bt = context; +} diff --git a/applications/external/bad_bt/scenes/bad_bt_scene_work.c b/applications/external/bad_bt/scenes/bad_bt_scene_work.c new file mode 100644 index 000000000..684bb8b74 --- /dev/null +++ b/applications/external/bad_bt/scenes/bad_bt_scene_work.c @@ -0,0 +1,56 @@ +#include "../helpers/ducky_script.h" +#include "../bad_bt_app.h" +#include "../views/bad_bt_view.h" +#include +#include "toolbox/path.h" + +void bad_bt_scene_work_button_callback(InputKey key, void* context) { + furi_assert(context); + BadBtApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, key); +} + +bool bad_bt_scene_work_on_event(void* context, SceneManagerEvent event) { + BadBtApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == InputKeyLeft) { + if(bad_bt_is_idle_state(app->bad_bt_view)) { + scene_manager_next_scene(app->scene_manager, BadBtSceneConfig); + } + consumed = true; + } else if(event.event == InputKeyOk) { + bad_bt_script_toggle(app->bad_bt_script); + consumed = true; + } + } else if(event.type == SceneManagerEventTypeTick) { + bad_bt_set_state(app->bad_bt_view, bad_bt_script_get_state(app->bad_bt_script)); + } + return consumed; +} + +void bad_bt_scene_work_on_enter(void* context) { + BadBtApp* app = context; + + FuriString* file_name; + file_name = furi_string_alloc(); + path_extract_filename(app->file_path, file_name, true); + bad_bt_set_file_name(app->bad_bt_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_bt_set_layout(app->bad_bt_view, furi_string_get_cstr(layout)); + furi_string_free(layout); + + bad_bt_set_state(app->bad_bt_view, bad_bt_script_get_state(app->bad_bt_script)); + + bad_bt_set_button_callback(app->bad_bt_view, bad_bt_scene_work_button_callback, app); + view_dispatcher_switch_to_view(app->view_dispatcher, BadBtAppViewWork); +} + +void bad_bt_scene_work_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/external/bad_bt/views/bad_bt_view.c b/applications/external/bad_bt/views/bad_bt_view.c new file mode 100644 index 000000000..4a9bf589c --- /dev/null +++ b/applications/external/bad_bt/views/bad_bt_view.c @@ -0,0 +1,233 @@ +#include "bad_bt_view.h" +#include "../helpers/ducky_script.h" +#include "../bad_bt_app.h" +#include +#include +#include + +#define MAX_NAME_LEN 64 + +typedef struct { + char file_name[MAX_NAME_LEN]; + char layout[MAX_NAME_LEN]; + BadBtState state; + uint8_t anim_frame; +} BadBtModel; + +static void bad_bt_draw_callback(Canvas* canvas, void* _model) { + BadBtModel* model = _model; + + FuriString* disp_str; + disp_str = furi_string_alloc_set("(BT) "); + 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_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, ')'); + } + 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); + + if((model->state.state == BadBtStateIdle) || (model->state.state == BadBtStateDone) || + (model->state.state == BadBtStateNotConnected)) { + elements_button_center(canvas, "Run"); + elements_button_left(canvas, "Config"); + } else if((model->state.state == BadBtStateRunning) || (model->state.state == BadBtStateDelay)) { + elements_button_center(canvas, "Stop"); + } else if(model->state.state == BadBtStateWaitForBtn) { + elements_button_center(canvas, "Press to continue"); + } else if(model->state.state == BadBtStateWillRun) { + elements_button_center(canvas, "Cancel"); + } + + if(model->state.state == BadBtStateNotConnected) { + 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(model->state.state == BadBtStateWillRun) { + 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(model->state.state == BadBtStateFileError) { + 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 == BadBtStateScriptError) { + 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(model->state.state == BadBtStateIdle) { + 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 == BadBtStateRunning) { + 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 == BadBtStateDone) { + 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 == BadBtStateDelay) { + 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); +} + +static bool bad_bt_input_callback(InputEvent* event, void* context) { + furi_assert(context); + BadBt* bad_bt = context; + bool consumed = false; + + if(event->type == InputTypeShort) { + if((event->key == InputKeyLeft) || (event->key == InputKeyOk)) { + consumed = true; + furi_assert(bad_bt->callback); + bad_bt->callback(event->key, bad_bt->context); + } + } + + return consumed; +} + +BadBt* bad_bt_alloc() { + BadBt* bad_bt = malloc(sizeof(BadBt)); + + bad_bt->view = view_alloc(); + view_allocate_model(bad_bt->view, ViewModelTypeLocking, sizeof(BadBtModel)); + view_set_context(bad_bt->view, bad_bt); + view_set_draw_callback(bad_bt->view, bad_bt_draw_callback); + view_set_input_callback(bad_bt->view, bad_bt_input_callback); + + return bad_bt; +} + +void bad_bt_free(BadBt* bad_bt) { + furi_assert(bad_bt); + view_free(bad_bt->view); + free(bad_bt); +} + +View* bad_bt_get_view(BadBt* bad_bt) { + furi_assert(bad_bt); + return bad_bt->view; +} + +void bad_bt_set_button_callback(BadBt* bad_bt, BadBtButtonCallback callback, void* context) { + furi_assert(bad_bt); + furi_assert(callback); + with_view_model( + bad_bt->view, + BadBtModel * model, + { + UNUSED(model); + bad_bt->callback = callback; + bad_bt->context = context; + }, + true); +} + +void bad_bt_set_file_name(BadBt* bad_bt, const char* name) { + furi_assert(name); + with_view_model( + bad_bt->view, BadBtModel * model, { strlcpy(model->file_name, name, MAX_NAME_LEN); }, true); +} + +void bad_bt_set_layout(BadBt* bad_bt, const char* layout) { + furi_assert(layout); + with_view_model( + bad_bt->view, BadBtModel * model, { strlcpy(model->layout, layout, MAX_NAME_LEN); }, true); +} + +void bad_bt_set_state(BadBt* bad_bt, BadBtState* st) { + furi_assert(st); + uint32_t pin = 0; + if(bad_bt->context != NULL) { + BadBtApp* app = bad_bt->context; + if(app->bt != NULL) { + pin = app->bt->pin; + } + } + st->pin = pin; + with_view_model( + bad_bt->view, + BadBtModel * model, + { + memcpy(&(model->state), st, sizeof(BadBtState)); + model->anim_frame ^= 1; + }, + true); +} + +bool bad_bt_is_idle_state(BadBt* bad_bt) { + bool is_idle = false; + with_view_model( + bad_bt->view, + BadBtModel * model, + { + if((model->state.state == BadBtStateIdle) || (model->state.state == BadBtStateDone) || + (model->state.state == BadBtStateNotConnected)) { + is_idle = true; + } + }, + false); + return is_idle; +} diff --git a/applications/external/bad_bt/views/bad_bt_view.h b/applications/external/bad_bt/views/bad_bt_view.h new file mode 100644 index 000000000..850a71057 --- /dev/null +++ b/applications/external/bad_bt/views/bad_bt_view.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +typedef void (*BadBtButtonCallback)(InputKey key, void* context); + +typedef struct { + View* view; + BadBtButtonCallback callback; + void* context; +} BadBt; + +typedef struct BadBtState BadBtState; + +BadBt* bad_bt_alloc(); + +void bad_bt_free(BadBt* bad_bt); + +View* bad_bt_get_view(BadBt* bad_bt); + +void bad_bt_set_button_callback(BadBt* bad_bt, BadBtButtonCallback callback, void* context); + +void bad_bt_set_file_name(BadBt* bad_bt, const char* name); + +void bad_bt_set_layout(BadBt* bad_bt, const char* layout); + +void bad_bt_set_state(BadBt* bad_bt, BadBtState* st); + +bool bad_bt_is_idle_state(BadBt* bad_bt); diff --git a/applications/external/totp/features_config.h b/applications/external/totp/features_config.h index 845ec1d35..683eff2fc 100644 --- a/applications/external/totp/features_config.h +++ b/applications/external/totp/features_config.h @@ -11,4 +11,4 @@ // End of list // Target firmware to build for -#define TOTP_TARGET_FIRMWARE TOTP_FIRMWARE_OFFICIAL_DEV \ No newline at end of file +#define TOTP_TARGET_FIRMWARE TOTP_FIRMWARE_XTREME \ 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 2dcea3485..a842aea45 100644 --- a/applications/services/bt/bt_service/bt.c +++ b/applications/services/bt/bt_service/bt.c @@ -61,6 +61,7 @@ static ViewPort* bt_pin_code_view_port_alloc(Bt* bt) { static void bt_pin_code_show(Bt* bt, uint32_t pin_code) { bt->pin_code = pin_code; + if(bt->suppress_pin_screen) return; notification_message(bt->notification, &sequence_display_backlight_on); gui_view_port_send_to_front(bt->gui, bt->pin_code_view_port); view_port_enabled_set(bt->pin_code_view_port, true); @@ -75,6 +76,8 @@ static void bt_pin_code_hide(Bt* bt) { static bool bt_pin_code_verify_event_handler(Bt* bt, uint32_t pin) { furi_assert(bt); + bt->pin_code = pin; + if(bt->suppress_pin_screen) return true; notification_message(bt->notification, &sequence_display_backlight_on); FuriString* pin_str; dialog_message_set_icon(bt->dialog_message, &I_BLE_Pairing_128x64, 0, 0); @@ -149,6 +152,8 @@ Bt* bt_alloc() { // API evnent bt->api_event = furi_event_flag_alloc(); + bt->pin = 0; + return bt; } @@ -214,6 +219,7 @@ static bool bt_on_gap_event_callback(GapEvent event, void* context) { furi_assert(context); Bt* bt = context; bool ret = false; + bt->pin = 0; if(event.type == GapEventTypeConnected) { // Update status bar @@ -270,12 +276,14 @@ static bool bt_on_gap_event_callback(GapEvent event, void* context) { furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk); ret = true; } else if(event.type == GapEventTypePinCodeShow) { + bt->pin = event.data.pin_code; BtMessage message = { .type = BtMessageTypePinCodeShow, .data.pin_code = event.data.pin_code}; furi_check( furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk); ret = true; } else if(event.type == GapEventTypePinCodeVerify) { + bt->pin = event.data.pin_code; ret = bt_pin_code_verify_event_handler(bt, event.data.pin_code); } else if(event.type == GapEventTypeUpdateMTU) { bt->max_packet_size = event.data.max_packet_size; @@ -368,6 +376,86 @@ static void bt_close_connection(Bt* bt) { furi_event_flag_set(bt->api_event, BT_API_UNLOCK_EVENT); } +static inline FuriHalBtProfile get_hal_bt_profile(BtProfile profile) { + if(profile == BtProfileHidKeyboard) { + return FuriHalBtProfileHidKeyboard; + } else { + return FuriHalBtProfileSerial; + } +} + +void bt_restart(Bt* bt) { + furi_hal_bt_change_app(get_hal_bt_profile(bt->profile), 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_BT_ADV_NAME_LENGTH]; + va_list args; + va_start(args, fmt); + vsnprintf(name, sizeof(name), fmt, args); + va_end(args); + furi_hal_bt_set_profile_adv_name(get_hal_bt_profile(bt->profile), name); + + bt_restart(bt); +} + +const char* bt_get_profile_adv_name(Bt* bt) { + furi_assert(bt); + return furi_hal_bt_get_profile_adv_name(get_hal_bt_profile(bt->profile)); +} + +void bt_set_profile_mac_address(Bt* bt, const uint8_t mac[6]) { + furi_assert(bt); + furi_assert(mac); + + furi_hal_bt_set_profile_mac_addr(get_hal_bt_profile(bt->profile), mac); + + bt_restart(bt); +} + +const uint8_t* bt_get_profile_mac_address(Bt* bt) { + furi_assert(bt); + return furi_hal_bt_get_profile_mac_addr(get_hal_bt_profile(bt->profile)); +} + +bool bt_remote_rssi(Bt* bt, uint8_t* rssi) { + furi_assert(bt); + + uint8_t rssi_val; + uint32_t since = furi_hal_bt_get_conn_rssi(&rssi_val); + + if(since == 0) return false; + + *rssi = rssi_val; + + return true; +} + +void bt_set_profile_pairing_method(Bt* bt, GapPairing pairing_method) { + furi_assert(bt); + furi_hal_bt_set_profile_pairing_method(get_hal_bt_profile(bt->profile), pairing_method); + bt_restart(bt); +} + +GapPairing bt_get_profile_pairing_method(Bt* bt) { + furi_assert(bt); + return furi_hal_bt_get_profile_pairing_method(get_hal_bt_profile(bt->profile)); +} + +void bt_disable_peer_key_update(Bt* bt) { + UNUSED(bt); + furi_hal_bt_set_key_storage_change_callback(NULL, NULL); +} + +void bt_enable_peer_key_update(Bt* bt) { + furi_assert(bt); + furi_hal_bt_set_key_storage_change_callback(bt_on_key_storage_change_callback, bt); +} + 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..3d96ce603 100644 --- a/applications/services/bt/bt_service/bt.h +++ b/applications/services/bt/bt_service/bt.h @@ -3,6 +3,8 @@ #include #include +#include + #ifdef __cplusplus extern "C" { #endif @@ -23,6 +25,11 @@ typedef enum { BtProfileHidKeyboard, } BtProfile; +typedef struct { + uint8_t rssi; + uint32_t since; +} BtRssi; + typedef void (*BtStatusChangedCallback)(BtStatus status, void* context); /** Change BLE Profile @@ -69,6 +76,32 @@ void bt_keys_storage_set_storage_path(Bt* bt, const char* keys_storage_path); */ void bt_keys_storage_set_default_path(Bt* bt); +// New methods + +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); + +bool bt_remote_rssi(Bt* bt, uint8_t* rssi); + +void bt_set_profile_pairing_method(Bt* bt, GapPairing pairing_method); +GapPairing bt_get_profile_pairing_method(Bt* bt); + +/** Stop saving new peer key to flash (in .bt.keys file) + * +*/ +void bt_disable_peer_key_update(Bt* bt); + +/** Enable saving peer key to internal flash (enable by default) + * + * @note This function should be called if bt_disable_peer_key_update was called before +*/ +void bt_enable_peer_key_update(Bt* bt); + #ifdef __cplusplus } #endif diff --git a/applications/services/bt/bt_service/bt_i.h b/applications/services/bt/bt_service/bt_i.h index c8a0e9965..41e5bcd8c 100644 --- a/applications/services/bt/bt_service/bt_i.h +++ b/applications/services/bt/bt_service/bt_i.h @@ -76,4 +76,6 @@ struct Bt { FuriEventFlag* api_event; BtStatusChangedCallback status_changed_cb; void* status_changed_ctx; + uint32_t pin; + bool suppress_pin_screen; }; diff --git a/applications/services/gui/elements.c b/applications/services/gui/elements.c index 90682d68e..4d990c14e 100644 --- a/applications/services/gui/elements.c +++ b/applications/services/gui/elements.c @@ -573,6 +573,64 @@ void elements_string_fit_width(Canvas* canvas, FuriString* string, uint8_t width } } +void elements_scrollable_text_line_str( + Canvas* canvas, + uint8_t x, + uint8_t y, + uint8_t width, + const char* string, + size_t scroll, + bool ellipsis, + bool centered) { + FuriString* line = furi_string_alloc_set_str(string); + + size_t len_px = canvas_string_width(canvas, furi_string_get_cstr(line)); + if(len_px > width) { + if(centered) { + centered = false; + x -= width / 2; + } + + if(ellipsis) { + width -= canvas_string_width(canvas, "..."); + } + + // Calculate scroll size + size_t scroll_size = furi_string_size(line); + size_t right_width = 0; + for(size_t i = scroll_size - 1; i > 0; i--) { + right_width += canvas_glyph_width(canvas, furi_string_get_char(line, i)); + if(right_width > width) break; + scroll_size--; + if(!scroll_size) break; + } + // Ensure that we have something to scroll + if(scroll_size) { + scroll_size += 3; + scroll = scroll % scroll_size; + furi_string_right(line, scroll); + } + + len_px = canvas_string_width(canvas, furi_string_get_cstr(line)); + while(len_px > width) { + furi_string_left(line, furi_string_size(line) - 1); + len_px = canvas_string_width(canvas, furi_string_get_cstr(line)); + } + + if(ellipsis) { + furi_string_cat(line, "..."); + } + } + + if(centered) { + canvas_draw_str_aligned( + canvas, x, y, AlignCenter, AlignBottom, furi_string_get_cstr(line)); + } else { + canvas_draw_str(canvas, x, y, furi_string_get_cstr(line)); + } + furi_string_free(line); +} + void elements_scrollable_text_line( Canvas* canvas, uint8_t x, diff --git a/applications/services/gui/elements.h b/applications/services/gui/elements.h index 04ca357b8..bdc43d09a 100644 --- a/applications/services/gui/elements.h +++ b/applications/services/gui/elements.h @@ -228,6 +228,16 @@ void elements_scrollable_text_line( size_t scroll, bool ellipsis); +void elements_scrollable_text_line_str( + Canvas* canvas, + uint8_t x, + uint8_t y, + uint8_t width, + const char* string, + size_t scroll, + bool ellipsis, + bool centered); + /** Draw text box element * * @param canvas Canvas instance diff --git a/applications/services/gui/modules/submenu.c b/applications/services/gui/modules/submenu.c index 66b264458..5f47ac179 100644 --- a/applications/services/gui/modules/submenu.c +++ b/applications/services/gui/modules/submenu.c @@ -1,11 +1,14 @@ #include "submenu.h" +#include +#include #include #include #include struct Submenu { View* view; + FuriTimer* locked_timer; }; typedef struct { @@ -13,6 +16,8 @@ typedef struct { uint32_t index; SubmenuItemCallback callback; void* callback_context; + bool locked; + FuriString* locked_message; } SubmenuItem; static void SubmenuItem_init(SubmenuItem* item) { @@ -20,6 +25,8 @@ static void SubmenuItem_init(SubmenuItem* item) { item->index = 0; item->callback = NULL; item->callback_context = NULL; + item->locked = false; + item->locked_message = furi_string_alloc(); } static void SubmenuItem_init_set(SubmenuItem* item, const SubmenuItem* src) { @@ -27,6 +34,8 @@ static void SubmenuItem_init_set(SubmenuItem* item, const SubmenuItem* src) { item->index = src->index; item->callback = src->callback; item->callback_context = src->callback_context; + item->locked = src->locked; + item->locked_message = furi_string_alloc_set(src->locked_message); } static void SubmenuItem_set(SubmenuItem* item, const SubmenuItem* src) { @@ -34,10 +43,13 @@ static void SubmenuItem_set(SubmenuItem* item, const SubmenuItem* src) { item->index = src->index; item->callback = src->callback; item->callback_context = src->callback_context; + item->locked = src->locked; + furi_string_set(item->locked_message, src->locked_message); } static void SubmenuItem_clear(SubmenuItem* item) { furi_string_free(item->label); + furi_string_free(item->locked_message); } ARRAY_DEF( @@ -53,6 +65,7 @@ typedef struct { FuriString* header; size_t position; size_t window_position; + bool locked_message_visible; } SubmenuModel; static void submenu_process_up(Submenu* submenu); @@ -63,7 +76,12 @@ static void submenu_view_draw_callback(Canvas* canvas, void* _model) { SubmenuModel* model = _model; const uint8_t item_height = 16; - const uint8_t item_width = 123; + uint8_t item_width = 123; + + if(canvas->orientation == CanvasOrientationVertical || + canvas->orientation == CanvasOrientationVerticalFlip) { + item_width = 60; + } canvas_clear(canvas); @@ -96,9 +114,18 @@ static void submenu_view_draw_callback(Canvas* canvas, void* _model) { canvas_set_color(canvas, ColorBlack); } + if(SubmenuItemArray_cref(it)->locked) { + canvas_draw_icon( + canvas, + 110, + y_offset + (item_position * item_height) + item_height - 12, + &I_Lock_7x8); + } + FuriString* disp_str; disp_str = furi_string_alloc_set(SubmenuItemArray_cref(it)->label); - elements_string_fit_width(canvas, disp_str, item_width - 11); + elements_string_fit_width( + canvas, disp_str, item_width - (SubmenuItemArray_cref(it)->locked ? 25 : 11)); canvas_draw_str( canvas, @@ -113,6 +140,23 @@ static void submenu_view_draw_callback(Canvas* canvas, void* _model) { } elements_scrollbar(canvas, model->position, SubmenuItemArray_size(model->items)); + + if(model->locked_message_visible) { + canvas_set_color(canvas, ColorWhite); + canvas_draw_box(canvas, 8, 10, 110, 48); + canvas_set_color(canvas, ColorBlack); + canvas_draw_icon(canvas, 10, 14, &I_WarningDolphin_45x42); + canvas_draw_rframe(canvas, 8, 8, 112, 50, 3); + canvas_draw_rframe(canvas, 9, 9, 110, 48, 2); + elements_multiline_text_aligned( + canvas, + 84, + 32, + AlignCenter, + AlignCenter, + furi_string_get_cstr( + SubmenuItemArray_get(model->items, model->position)->locked_message)); + } } static bool submenu_view_input_callback(InputEvent* event, void* context) { @@ -120,7 +164,19 @@ static bool submenu_view_input_callback(InputEvent* event, void* context) { furi_assert(submenu); bool consumed = false; - if(event->type == InputTypeShort) { + bool locked_message_visible = false; + with_view_model( + submenu->view, + SubmenuModel * model, + { locked_message_visible = model->locked_message_visible; }, + false); + + if((!(event->type == InputTypePress) && !(event->type == InputTypeRelease)) && + locked_message_visible) { + with_view_model( + submenu->view, SubmenuModel * model, { model->locked_message_visible = false; }, true); + consumed = true; + } else if(event->type == InputTypeShort) { switch(event->key) { case InputKeyUp: consumed = true; @@ -150,6 +206,14 @@ static bool submenu_view_input_callback(InputEvent* event, void* context) { return consumed; } +void submenu_timer_callback(void* context) { + furi_assert(context); + Submenu* submenu = context; + + with_view_model( + submenu->view, SubmenuModel * model, { model->locked_message_visible = false; }, true); +} + Submenu* submenu_alloc() { Submenu* submenu = malloc(sizeof(Submenu)); submenu->view = view_alloc(); @@ -158,6 +222,8 @@ Submenu* submenu_alloc() { view_set_draw_callback(submenu->view, submenu_view_draw_callback); view_set_input_callback(submenu->view, submenu_view_input_callback); + submenu->locked_timer = furi_timer_alloc(submenu_timer_callback, FuriTimerTypeOnce, submenu); + with_view_model( submenu->view, SubmenuModel * model, @@ -183,6 +249,8 @@ void submenu_free(Submenu* submenu) { SubmenuItemArray_clear(model->items); }, true); + furi_timer_stop(submenu->locked_timer); + furi_timer_free(submenu->locked_timer); view_free(submenu->view); free(submenu); } @@ -198,9 +266,23 @@ void submenu_add_item( uint32_t index, SubmenuItemCallback callback, void* callback_context) { + submenu_add_lockable_item(submenu, label, index, callback, callback_context, false, NULL); +} + +void submenu_add_lockable_item( + Submenu* submenu, + const char* label, + uint32_t index, + SubmenuItemCallback callback, + void* callback_context, + bool locked, + const char* locked_message) { SubmenuItem* item = NULL; furi_assert(label); furi_assert(submenu); + if(locked) { + furi_assert(locked_message); + } with_view_model( submenu->view, @@ -211,6 +293,10 @@ void submenu_add_item( item->index = index; item->callback = callback; item->callback_context = callback_context; + item->locked = locked; + if(locked) { + furi_string_set_str(item->locked_message, locked_message); + } }, true); } @@ -328,10 +414,14 @@ void submenu_process_ok(Submenu* submenu) { if(model->position < items_size) { item = SubmenuItemArray_get(model->items, model->position); } + if(item && item->locked) { + model->locked_message_visible = true; + furi_timer_start(submenu->locked_timer, furi_kernel_get_tick_frequency() * 3); + } }, true); - if(item && item->callback) { + if(item && !item->locked && item->callback) { item->callback(item->callback_context, item->index); } } diff --git a/applications/services/gui/modules/submenu.h b/applications/services/gui/modules/submenu.h index f68abe83a..e7252eb33 100644 --- a/applications/services/gui/modules/submenu.h +++ b/applications/services/gui/modules/submenu.h @@ -53,6 +53,26 @@ void submenu_add_item( SubmenuItemCallback callback, void* callback_context); +/** Add lockable item to submenu + * + * @param submenu Submenu instance + * @param label menu item label + * @param index menu item index, used for callback, may be + * the same with other items + * @param callback menu item callback + * @param callback_context menu item callback context + * @param locked menu item locked status + * @param locked_message menu item locked message + */ +void submenu_add_lockable_item( + Submenu* submenu, + const char* label, + uint32_t index, + SubmenuItemCallback callback, + void* callback_context, + bool locked, + const char* locked_message); + /** Remove all items from submenu * * @param submenu Submenu instance diff --git a/applications/services/gui/modules/variable_item_list.c b/applications/services/gui/modules/variable_item_list.c index cf7f64ca3..93948f7a4 100644 --- a/applications/services/gui/modules/variable_item_list.c +++ b/applications/services/gui/modules/variable_item_list.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include @@ -11,6 +12,8 @@ struct VariableItem { FuriString* current_value_text; uint8_t values_count; VariableItemChangeCallback change_callback; + bool locked; + FuriString* locked_message; void* context; }; @@ -20,12 +23,16 @@ struct VariableItemList { View* view; VariableItemListEnterCallback callback; void* context; + FuriTimer* scroll_timer; + FuriTimer* locked_timer; }; typedef struct { VariableItemArray_t items; uint8_t position; uint8_t window_position; + size_t scroll_counter; + bool locked_message_visible; } VariableItemListModel; static void variable_item_list_process_up(VariableItemList* variable_item_list); @@ -56,31 +63,50 @@ static void variable_item_list_draw_callback(Canvas* canvas, void* _model) { const VariableItem* item = VariableItemArray_cref(it); uint8_t item_y = y_offset + (item_position * item_height); uint8_t item_text_y = item_y + item_height - 4; + size_t scroll_counter = 0; if(position == model->position) { canvas_set_color(canvas, ColorBlack); elements_slightly_rounded_box(canvas, 0, item_y + 1, item_width, item_height - 2); canvas_set_color(canvas, ColorWhite); + scroll_counter = model->scroll_counter; + if(scroll_counter < 1) { + scroll_counter = 0; + } else { + scroll_counter -= 1; + } } else { canvas_set_color(canvas, ColorBlack); } - canvas_draw_str(canvas, 6, item_text_y, item->label); - - if(item->current_value_index > 0) { - canvas_draw_str(canvas, 73, item_text_y, "<"); + if(item->current_value_index == 0 && furi_string_empty(item->current_value_text)) { + // Only left text, no right text + canvas_draw_str(canvas, 6, item_text_y, item->label); + } else { + elements_scrollable_text_line_str( + canvas, 6, item_text_y, 66, item->label, scroll_counter, false, false); } - canvas_draw_str_aligned( - canvas, - (115 + 73) / 2 + 1, - item_text_y, - AlignCenter, - AlignBottom, - furi_string_get_cstr(item->current_value_text)); + if(item->locked) { + canvas_draw_icon(canvas, 110, item_text_y - 8, &I_Lock_7x8); + } else { + if(item->current_value_index > 0) { + canvas_draw_str(canvas, 73, item_text_y, "<"); + } - if(item->current_value_index < (item->values_count - 1)) { - canvas_draw_str(canvas, 115, item_text_y, ">"); + elements_scrollable_text_line_str( + canvas, + (115 + 73) / 2 + 1, + item_text_y, + 37, + furi_string_get_cstr(item->current_value_text), + scroll_counter, + false, + true); + + if(item->current_value_index < (item->values_count - 1)) { + canvas_draw_str(canvas, 115, item_text_y, ">"); + } } } @@ -88,6 +114,23 @@ static void variable_item_list_draw_callback(Canvas* canvas, void* _model) { } elements_scrollbar(canvas, model->position, VariableItemArray_size(model->items)); + + if(model->locked_message_visible) { + canvas_set_color(canvas, ColorWhite); + canvas_draw_box(canvas, 8, 10, 110, 48); + canvas_set_color(canvas, ColorBlack); + canvas_draw_icon(canvas, 10, 14, &I_WarningDolphin_45x42); + canvas_draw_rframe(canvas, 8, 8, 112, 50, 3); + canvas_draw_rframe(canvas, 9, 9, 110, 48, 2); + elements_multiline_text_aligned( + canvas, + 84, + 32, + AlignCenter, + AlignCenter, + furi_string_get_cstr( + VariableItemArray_get(model->items, model->position)->locked_message)); + } } void variable_item_list_set_selected_item(VariableItemList* variable_item_list, uint8_t index) { @@ -130,7 +173,22 @@ static bool variable_item_list_input_callback(InputEvent* event, void* context) furi_assert(variable_item_list); bool consumed = false; - if(event->type == InputTypeShort) { + bool locked_message_visible = false; + with_view_model( + variable_item_list->view, + VariableItemListModel * model, + { locked_message_visible = model->locked_message_visible; }, + false); + + if((!(event->type == InputTypePress) && !(event->type == InputTypeRelease)) && + locked_message_visible) { + with_view_model( + variable_item_list->view, + VariableItemListModel * model, + { model->locked_message_visible = false; }, + true); + consumed = true; + } else if(event->type == InputTypeShort) { switch(event->key) { case InputKeyUp: consumed = true; @@ -198,6 +256,7 @@ void variable_item_list_process_up(VariableItemList* variable_item_list) { model->window_position = model->position - (items_on_screen - 1); } } + model->scroll_counter = 0; }, true); } @@ -219,6 +278,7 @@ void variable_item_list_process_down(VariableItemList* variable_item_list) { model->position = 0; model->window_position = 0; } + model->scroll_counter = 0; }, true); } @@ -248,8 +308,13 @@ void variable_item_list_process_left(VariableItemList* variable_item_list) { VariableItemListModel * model, { VariableItem* item = variable_item_list_get_selected_item(model); - if(item->current_value_index > 0) { + if(item->locked) { + model->locked_message_visible = true; + furi_timer_start( + variable_item_list->locked_timer, furi_kernel_get_tick_frequency() * 3); + } else if(item->current_value_index > 0) { item->current_value_index--; + model->scroll_counter = 0; if(item->change_callback) { item->change_callback(item); } @@ -264,8 +329,13 @@ void variable_item_list_process_right(VariableItemList* variable_item_list) { VariableItemListModel * model, { VariableItem* item = variable_item_list_get_selected_item(model); - if(item->current_value_index < (item->values_count - 1)) { + if(item->locked) { + model->locked_message_visible = true; + furi_timer_start( + variable_item_list->locked_timer, furi_kernel_get_tick_frequency() * 3); + } else if(item->current_value_index < (item->values_count - 1)) { item->current_value_index++; + model->scroll_counter = 0; if(item->change_callback) { item->change_callback(item); } @@ -279,11 +349,36 @@ void variable_item_list_process_ok(VariableItemList* variable_item_list) { variable_item_list->view, VariableItemListModel * model, { - if(variable_item_list->callback) { + VariableItem* item = variable_item_list_get_selected_item(model); + if(item->locked) { + model->locked_message_visible = true; + furi_timer_start( + variable_item_list->locked_timer, furi_kernel_get_tick_frequency() * 3); + } else if(variable_item_list->callback) { variable_item_list->callback(variable_item_list->context, model->position); } }, - false); + true); +} + +static void variable_item_list_scroll_timer_callback(void* context) { + VariableItemList* variable_item_list = context; + with_view_model( + variable_item_list->view, + VariableItemListModel * model, + { model->scroll_counter++; }, + true); +} + +void variable_item_list_locked_timer_callback(void* context) { + furi_assert(context); + VariableItemList* variable_item_list = context; + + with_view_model( + variable_item_list->view, + VariableItemListModel * model, + { model->locked_message_visible = false; }, + true); } VariableItemList* variable_item_list_alloc() { @@ -295,6 +390,9 @@ VariableItemList* variable_item_list_alloc() { view_set_draw_callback(variable_item_list->view, variable_item_list_draw_callback); view_set_input_callback(variable_item_list->view, variable_item_list_input_callback); + variable_item_list->locked_timer = furi_timer_alloc( + variable_item_list_locked_timer_callback, FuriTimerTypeOnce, variable_item_list); + with_view_model( variable_item_list->view, VariableItemListModel * model, @@ -302,8 +400,12 @@ VariableItemList* variable_item_list_alloc() { VariableItemArray_init(model->items); model->position = 0; model->window_position = 0; + model->scroll_counter = 0; }, true); + variable_item_list->scroll_timer = furi_timer_alloc( + variable_item_list_scroll_timer_callback, FuriTimerTypePeriodic, variable_item_list); + furi_timer_start(variable_item_list->scroll_timer, 333); return variable_item_list; } @@ -319,10 +421,15 @@ void variable_item_list_free(VariableItemList* variable_item_list) { for(VariableItemArray_it(it, model->items); !VariableItemArray_end_p(it); VariableItemArray_next(it)) { furi_string_free(VariableItemArray_ref(it)->current_value_text); + furi_string_free(VariableItemArray_ref(it)->locked_message); } VariableItemArray_clear(model->items); }, false); + furi_timer_stop(variable_item_list->scroll_timer); + furi_timer_free(variable_item_list->scroll_timer); + furi_timer_stop(variable_item_list->locked_timer); + furi_timer_free(variable_item_list->locked_timer); view_free(variable_item_list->view); free(variable_item_list); } @@ -338,6 +445,7 @@ void variable_item_list_reset(VariableItemList* variable_item_list) { for(VariableItemArray_it(it, model->items); !VariableItemArray_end_p(it); VariableItemArray_next(it)) { furi_string_free(VariableItemArray_ref(it)->current_value_text); + furi_string_free(VariableItemArray_ref(it)->locked_message); } VariableItemArray_reset(model->items); }, @@ -370,6 +478,8 @@ VariableItem* variable_item_list_add( item->context = context; item->current_value_index = 0; item->current_value_text = furi_string_alloc(); + item->locked = false; + item->locked_message = furi_string_alloc(); }, true); @@ -404,6 +514,14 @@ void variable_item_set_current_value_text(VariableItem* item, const char* curren furi_string_set(item->current_value_text, current_value_text); } +void variable_item_set_locked(VariableItem* item, bool locked, const char* locked_message) { + item->locked = locked; + if(locked) { + furi_assert(locked_message); + furi_string_set(item->locked_message, locked_message); + } +} + uint8_t variable_item_get_current_value_index(VariableItem* item) { return item->current_value_index; } diff --git a/applications/services/gui/modules/variable_item_list.h b/applications/services/gui/modules/variable_item_list.h index db2a58993..db8b1788f 100644 --- a/applications/services/gui/modules/variable_item_list.h +++ b/applications/services/gui/modules/variable_item_list.h @@ -95,6 +95,14 @@ void variable_item_set_values_count(VariableItem* item, uint8_t values_count); */ void variable_item_set_current_value_text(VariableItem* item, const char* current_value_text); +/** Set item locked state and text + * + * @param item VariableItem* instance + * @param locked Is item locked boolean + * @param locked_message The locked message text + */ +void variable_item_set_locked(VariableItem* item, bool locked, const char* locked_message); + /** Get item current selected index * * @param item VariableItem* instance 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 31921b9f3..d009ae2d5 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 @@ -1,5 +1,6 @@ #include "../bt_settings_app.h" #include +#include void bt_settings_scene_forget_dev_confirm_dialog_callback(DialogExResult result, void* context) { furi_assert(context); @@ -30,6 +31,14 @@ bool bt_settings_scene_forget_dev_confirm_on_event(void* context, SceneManagerEv consumed = scene_manager_previous_scene(app->scene_manager); } else if(event.event == DialogExResultRight) { bt_forget_bonded_devices(app->bt); + + // Also remove keys of BadBT, Bluetooth Remote, TOTP Authenticator + Storage* storage = furi_record_open(RECORD_STORAGE); + storage_simply_remove(storage, EXT_PATH("badbt/.badbt.keys")); + storage_simply_remove(storage, EXT_PATH("apps_data/hid_ble/.bt_hid.keys")); + storage_simply_remove(storage, EXT_PATH("authenticator/.bt_hid.keys")); + furi_record_close(RECORD_STORAGE); + scene_manager_next_scene(app->scene_manager, BtSettingsAppSceneForgetDevSuccess); consumed = true; } diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 96006c43c..7eac9ab00 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -586,11 +586,20 @@ Function,+,ble_glue_start,_Bool, Function,+,ble_glue_thread_stop,void, 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_disable_peer_key_update,void,Bt* Function,+,bt_disconnect,void,Bt* +Function,+,bt_enable_peer_key_update,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_get_profile_pairing_method,GapPairing,Bt* Function,+,bt_keys_storage_set_default_path,void,Bt* Function,+,bt_keys_storage_set_storage_path,void,"Bt*, const char*" +Function,+,bt_remote_rssi,_Bool,"Bt*, uint8_t*" 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_profile_pairing_method,void,"Bt*, GapPairing" Function,+,bt_set_status_changed_callback,void,"Bt*, BtStatusChangedCallback, void*" Function,+,buffered_file_stream_alloc,Stream*,Storage* Function,+,buffered_file_stream_close,_Bool,Stream* @@ -816,6 +825,7 @@ Function,+,elements_multiline_text_framed,void,"Canvas*, uint8_t, uint8_t, const Function,+,elements_progress_bar,void,"Canvas*, uint8_t, uint8_t, uint8_t, float" Function,+,elements_progress_bar_with_text,void,"Canvas*, uint8_t, uint8_t, uint8_t, float, const char*" Function,+,elements_scrollable_text_line,void,"Canvas*, uint8_t, uint8_t, uint8_t, FuriString*, size_t, _Bool" +Function,+,elements_scrollable_text_line_str,void,"Canvas*, uint8_t, uint8_t, uint8_t, const char*, size_t, _Bool, _Bool" Function,+,elements_scrollbar,void,"Canvas*, uint16_t, uint16_t" Function,+,elements_scrollbar_pos,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint16_t, uint16_t" Function,+,elements_slightly_rounded_box,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" @@ -1037,14 +1047,19 @@ Function,+,furi_hal_bt_change_app,_Bool,"FuriHalBtProfile, GapEventCallback, voi 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_conn_rssi,uint32_t,uint8_t* Function,-,furi_hal_bt_get_hardfault_info,const FuriHalBtHardfaultInfo*, 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_profile_pairing_method,GapPairing,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_get_led_state,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, @@ -1059,6 +1074,7 @@ Function,-,furi_hal_bt_init,void, Function,+,furi_hal_bt_is_active,_Bool, Function,+,furi_hal_bt_is_alive,_Bool, Function,+,furi_hal_bt_is_ble_gatt_gap_supported,_Bool, +Function,+,furi_hal_bt_is_connected,_Bool, Function,+,furi_hal_bt_is_testing_supported,_Bool, Function,+,furi_hal_bt_lock_core2,void, Function,+,furi_hal_bt_nvm_sram_sem_acquire,void, @@ -1071,6 +1087,9 @@ 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[( 18 + 1 )]" +Function,+,furi_hal_bt_set_profile_mac_addr,void,"FuriHalBtProfile, const uint8_t[( 6 )]" +Function,+,furi_hal_bt_set_profile_pairing_method,void,"FuriHalBtProfile, GapPairing" 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" @@ -1633,6 +1652,7 @@ Function,-,gamma,double,double Function,-,gamma_r,double,"double, int*" Function,-,gammaf,float,float Function,-,gammaf_r,float,"float, int*" +Function,+,gap_get_remote_conn_rssi,uint32_t,int8_t* Function,-,gap_get_state,GapState, Function,-,gap_init,_Bool,"GapConfig*, GapEventCallback, void*" Function,-,gap_start_advertising,void, @@ -3334,6 +3354,7 @@ Function,+,subghz_worker_set_pair_callback,void,"SubGhzWorker*, SubGhzWorkerPair Function,+,subghz_worker_start,void,SubGhzWorker* Function,+,subghz_worker_stop,void,SubGhzWorker* Function,+,submenu_add_item,void,"Submenu*, const char*, uint32_t, SubmenuItemCallback, void*" +Function,+,submenu_add_lockable_item,void,"Submenu*, const char*, uint32_t, SubmenuItemCallback, void*, _Bool, const char*" Function,+,submenu_alloc,Submenu*, Function,+,submenu_free,void,Submenu* Function,+,submenu_get_view,View*,Submenu* @@ -4557,6 +4578,7 @@ Function,+,variable_item_list_set_enter_callback,void,"VariableItemList*, Variab Function,+,variable_item_list_set_selected_item,void,"VariableItemList*, uint8_t" Function,+,variable_item_set_current_value_index,void,"VariableItem*, uint8_t" Function,+,variable_item_set_current_value_text,void,"VariableItem*, const char*" +Function,+,variable_item_set_locked,void,"VariableItem*, _Bool, const char*" Function,+,variable_item_set_values_count,void,"VariableItem*, uint8_t" Function,-,vasiprintf,int,"char**, const char*, __gnuc_va_list" Function,-,vasniprintf,char*,"char*, size_t*, const char*, __gnuc_va_list" diff --git a/firmware/targets/f7/ble_glue/gap.c b/firmware/targets/f7/ble_glue/gap.c index f0a9ced3c..f2fb3e388 100644 --- a/firmware/targets/f7/ble_glue/gap.c +++ b/firmware/targets/f7/ble_glue/gap.c @@ -28,6 +28,8 @@ typedef struct { GapConfig* config; GapConnectionParams connection_params; GapState state; + int8_t conn_rssi; + uint32_t time_rssi_sample; FuriMutex* state_mutex; GapEventCallback on_event_cb; void* context; @@ -56,6 +58,19 @@ static Gap* gap = NULL; static void gap_advertise_start(GapState new_state); static int32_t gap_app(void* context); +/** function for updating rssi informations in global Gap object + * +*/ +static inline void fetch_rssi() { + uint8_t ret_rssi = 127; + if(hci_read_rssi(gap->service.connection_handle, &ret_rssi) == BLE_STATUS_SUCCESS) { + gap->conn_rssi = (int8_t)ret_rssi; + gap->time_rssi_sample = furi_get_tick(); + return; + } + FURI_LOG_D(TAG, "Failed to read RSSI"); +} + static void gap_verify_connection_parameters(Gap* gap) { furi_assert(gap); @@ -128,6 +143,9 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification(void* pckt) { gap->connection_params.supervisor_timeout = event->Supervision_Timeout; FURI_LOG_I(TAG, "Connection parameters event complete"); gap_verify_connection_parameters(gap); + + // Save rssi for current connection + fetch_rssi(); break; } @@ -162,6 +180,9 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification(void* pckt) { gap->service.connection_handle = event->Connection_Handle; gap_verify_connection_parameters(gap); + + // Save rssi for current connection + fetch_rssi(); // Start pairing by sending security request aci_gap_slave_security_req(event->Connection_Handle); } break; @@ -242,6 +263,9 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification(void* pckt) { pairing_complete->Status); aci_gap_terminate(gap->service.connection_handle, 5); } else { + // Save RSSI + fetch_rssi(); + FURI_LOG_I(TAG, "Pairing complete"); GapEvent event = {.type = GapEventTypeConnected}; gap->on_event_cb(event, gap->context); //-V595 @@ -310,7 +334,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, @@ -345,21 +369,34 @@ static void gap_init_svc(Gap* gap) { hci_le_set_default_phy(ALL_PHYS_PREFERENCE, TX_2M_PREFERRED, RX_2M_PREFERRED); // Set I/O capability bool keypress_supported = false; + // New things below + uint8_t conf_mitm = CFG_MITM_PROTECTION; + uint8_t conf_used_fixed_pin = CFG_USED_FIXED_PIN; + bool conf_bonding = gap->config->bonding_mode; + if(gap->config->pairing_method == GapPairingPinCodeShow) { aci_gap_set_io_capability(IO_CAP_DISPLAY_ONLY); } else if(gap->config->pairing_method == GapPairingPinCodeVerifyYesNo) { aci_gap_set_io_capability(IO_CAP_DISPLAY_YES_NO); keypress_supported = true; + } else if(gap->config->pairing_method == GapPairingNone) { + // Just works pairing method (IOS accept it, it seems android and linux doesn't) + conf_mitm = 0; + conf_used_fixed_pin = 0; + conf_bonding = false; + // if just works isn't supported, we want the numeric comparaison method + aci_gap_set_io_capability(IO_CAP_DISPLAY_YES_NO); + keypress_supported = true; } // Setup authentication aci_gap_set_authentication_requirement( - gap->config->bonding_mode, - CFG_MITM_PROTECTION, + conf_bonding, + conf_mitm, CFG_SC_SUPPORT, keypress_supported, CFG_ENCRYPTION_KEY_SIZE_MIN, CFG_ENCRYPTION_KEY_SIZE_MAX, - CFG_USED_FIXED_PIN, + conf_used_fixed_pin, // 0x0 for no pin 0, CFG_IDENTITY_ADDRESS); // Configure whitelist @@ -488,6 +525,9 @@ bool gap_init(GapConfig* config, GapEventCallback on_event_cb, void* context) { gap->service.connection_handle = 0xFFFF; gap->enable_adv = true; + gap->conn_rssi = 127; + gap->time_rssi_sample = 0; + // Thread configuration gap->thread = furi_thread_alloc_ex("BleGapDriver", 1024, gap_app, gap); furi_thread_start(gap->thread); @@ -507,6 +547,17 @@ bool gap_init(GapConfig* config, GapEventCallback on_event_cb, void* context) { return true; } +// Get RSSI +uint32_t gap_get_remote_conn_rssi(int8_t* rssi) { + if(gap && gap->state == GapStateConnected) { + fetch_rssi(); + *rssi = gap->conn_rssi; + + if(gap->time_rssi_sample) return furi_get_tick() - gap->time_rssi_sample; + } + return 0; +} + GapState gap_get_state() { GapState state; if(gap) { diff --git a/firmware/targets/f7/ble_glue/gap.h b/firmware/targets/f7/ble_glue/gap.h index 1e207299f..7b317e06c 100644 --- a/firmware/targets/f7/ble_glue/gap.h +++ b/firmware/targets/f7/ble_glue/gap.h @@ -81,6 +81,8 @@ GapState gap_get_state(); void gap_thread_stop(); +uint32_t gap_get_remote_conn_rssi(int8_t* rssi); + #ifdef __cplusplus } #endif diff --git a/firmware/targets/f7/ble_glue/hid_service.c b/firmware/targets/f7/ble_glue/hid_service.c index 47d242d4d..a31d6015f 100644 --- a/firmware/targets/f7/ble_glue/hid_service.c +++ b/firmware/targets/f7/ble_glue/hid_service.c @@ -14,6 +14,11 @@ typedef struct { uint16_t report_map_char_handle; uint16_t info_char_handle; uint16_t ctrl_point_char_handle; + // led state + uint16_t led_state_char_handle; + uint16_t led_state_desc_handle; + HidLedStateEventCallback led_state_event_callback; + void* led_state_ctx; } HIDSvc; static HIDSvc* hid_svc = NULL; @@ -30,6 +35,32 @@ static SVCCTL_EvtAckStatus_t hid_svc_event_handler(void* event) { } else if(blecore_evt->ecode == ACI_GATT_SERVER_CONFIRMATION_VSEVT_CODE) { // Process notification confirmation ret = SVCCTL_EvtAckFlowEnable; + } else if(blecore_evt->ecode == ACI_GATT_WRITE_PERMIT_REQ_VSEVT_CODE) { + // Process write request + aci_gatt_write_permit_req_event_rp0* req = + (aci_gatt_write_permit_req_event_rp0*)blecore_evt->data; + + furi_check(hid_svc->led_state_event_callback && hid_svc->led_state_ctx); + + // this check is likely to be incorrect, it will actually work in our case + // but we need to investigate gatt api to see what is the rules + // that specify attibute handle value from char handle (or the reverse) + if(req->Attribute_Handle == (hid_svc->led_state_char_handle + 1)) { + hid_svc->led_state_event_callback(req->Data[0], hid_svc->led_state_ctx); + aci_gatt_write_resp( + req->Connection_Handle, + req->Attribute_Handle, + 0x00, /* write_status = 0 (no error))*/ + 0x00, /* err_code */ + req->Data_Length, + req->Data); + aci_gatt_write_char_value( + req->Connection_Handle, + hid_svc->led_state_char_handle, + req->Data_Length, + req->Data); + ret = SVCCTL_EvtAckFlowEnable; + } } } return ret; @@ -55,8 +86,8 @@ void hid_svc_start() { PRIMARY_SERVICE, 2 + /* protocol mode */ (4 * HID_SVC_INPUT_REPORT_COUNT) + (3 * HID_SVC_OUTPUT_REPORT_COUNT) + - (3 * HID_SVC_FEATURE_REPORT_COUNT) + 1 + 2 + 2 + - 2, /* Service + Report Map + HID Information + HID Control Point */ + (3 * HID_SVC_FEATURE_REPORT_COUNT) + 1 + 2 + 2 + 2 + + 4, /* Service + Report Map + HID Information + HID Control Point + LED state */ &hid_svc->svc_handle); if(status) { FURI_LOG_E(TAG, "Failed to add HID service: %d", status); @@ -198,6 +229,43 @@ void hid_svc_start() { } } #endif + // Add led state output report + char_uuid.Char_UUID_16 = REPORT_CHAR_UUID; + status = aci_gatt_add_char( + hid_svc->svc_handle, + UUID_TYPE_16, + &char_uuid, + 1, + CHAR_PROP_READ | CHAR_PROP_WRITE_WITHOUT_RESP | CHAR_PROP_WRITE, + ATTR_PERMISSION_NONE, + GATT_NOTIFY_ATTRIBUTE_WRITE | GATT_NOTIFY_WRITE_REQ_AND_WAIT_FOR_APPL_RESP, + 10, + CHAR_VALUE_LEN_CONSTANT, + &(hid_svc->led_state_char_handle)); + if(status) { + FURI_LOG_E(TAG, "Failed to add led state characteristic: %d", status); + } + + // Add led state char descriptor specifying it is an output report + uint8_t buf[2] = {HID_SVC_REPORT_COUNT + 1, 2}; + desc_uuid.Char_UUID_16 = REPORT_REFERENCE_DESCRIPTOR_UUID; + status = aci_gatt_add_char_desc( + hid_svc->svc_handle, + hid_svc->led_state_char_handle, + UUID_TYPE_16, + &desc_uuid, + HID_SVC_REPORT_REF_LEN, + HID_SVC_REPORT_REF_LEN, + buf, + ATTR_PERMISSION_NONE, + ATTR_ACCESS_READ_WRITE, + GATT_DONT_NOTIFY_EVENTS, + MIN_ENCRY_KEY_SIZE, + CHAR_VALUE_LEN_CONSTANT, + &(hid_svc->led_state_desc_handle)); + if(status) { + FURI_LOG_E(TAG, "Failed to add led state descriptor: %d", status); + } // Add Report Map characteristic char_uuid.Char_UUID_16 = REPORT_MAP_CHAR_UUID; status = aci_gatt_add_char( @@ -247,6 +315,9 @@ void hid_svc_start() { if(status) { FURI_LOG_E(TAG, "Failed to add control point characteristic: %d", status); } + + hid_svc->led_state_event_callback = NULL; + hid_svc->led_state_ctx = NULL; } bool hid_svc_update_report_map(const uint8_t* data, uint16_t len) { @@ -288,6 +359,15 @@ bool hid_svc_update_info(uint8_t* data, uint16_t len) { return true; } +void hid_svc_register_led_state_callback(HidLedStateEventCallback callback, void* context) { + furi_assert(hid_svc); + furi_assert(callback); + furi_assert(context); + + hid_svc->led_state_event_callback = callback; + hid_svc->led_state_ctx = context; +} + bool hid_svc_is_started() { return hid_svc != NULL; } @@ -320,6 +400,10 @@ void hid_svc_stop() { if(status) { FURI_LOG_E(TAG, "Failed to delete Control Point characteristic: %d", status); } + status = aci_gatt_del_char(hid_svc->svc_handle, hid_svc->led_state_char_handle); + if(status) { + FURI_LOG_E(TAG, "Failed to delete led state characteristic: %d", status); + } // Delete service status = aci_gatt_del_service(hid_svc->svc_handle); if(status) { diff --git a/firmware/targets/f7/ble_glue/hid_service.h b/firmware/targets/f7/ble_glue/hid_service.h index 723460d49..b8f6b244d 100644 --- a/firmware/targets/f7/ble_glue/hid_service.h +++ b/firmware/targets/f7/ble_glue/hid_service.h @@ -15,6 +15,8 @@ #define HID_SVC_REPORT_COUNT \ (HID_SVC_INPUT_REPORT_COUNT + HID_SVC_OUTPUT_REPORT_COUNT + HID_SVC_FEATURE_REPORT_COUNT) +typedef uint16_t (*HidLedStateEventCallback)(uint8_t state, void* ctx); + void hid_svc_start(); void hid_svc_stop(); @@ -26,3 +28,5 @@ bool hid_svc_update_report_map(const uint8_t* data, uint16_t len); bool hid_svc_update_input_report(uint8_t input_report_num, uint8_t* data, uint16_t len); bool hid_svc_update_info(uint8_t* data, uint16_t len); + +void hid_svc_register_led_state_callback(HidLedStateEventCallback callback, void* context); diff --git a/firmware/targets/f7/furi_hal/furi_hal_bt.c b/firmware/targets/f7/furi_hal/furi_hal_bt.c index 048a8b309..4e8b3091f 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_bt.c +++ b/firmware/targets/f7/furi_hal/furi_hal_bt.c @@ -199,26 +199,39 @@ 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; + // Configure GAP if(profile == FuriHalBtProfileSerial) { + // Set mac address + memcpy( + config->mac_address, furi_hal_version_get_ble_mac(), sizeof(config->mac_address)); + // Set advertise name + strlcpy( + config->adv_name, + furi_hal_version_get_ble_local_device_name_ptr(), + FURI_HAL_VERSION_DEVICE_NAME_LENGTH); + config->adv_service_uuid |= furi_hal_version_get_hw_color(); } else if(profile == FuriHalBtProfileHidKeyboard) { // Change MAC address for HID profile - config->mac_address[2]++; + uint8_t default_mac[sizeof(config->mac_address)] = FURI_HAL_BT_DEFAULT_MAC_ADDR; + const uint8_t* normal_mac = furi_hal_version_get_ble_mac(); + if(memcmp(config->mac_address, default_mac, sizeof(config->mac_address)) == 0) { + memcpy(config->mac_address, normal_mac, sizeof(config->mac_address)); + } + if(memcmp(config->mac_address, normal_mac, sizeof(config->mac_address)) == 0) { + config->mac_address[2]++; + } // Change name Flipper -> Control - const char* clicker_str = "Control"; - memcpy(&config->adv_name[1], clicker_str, strlen(clicker_str)); + if(strnlen(config->adv_name, FURI_HAL_VERSION_DEVICE_NAME_LENGTH) < 2 || + strnlen(config->adv_name + 1, FURI_HAL_VERSION_DEVICE_NAME_LENGTH) < 1) { + snprintf( + config->adv_name, + FURI_HAL_VERSION_DEVICE_NAME_LENGTH, + "%cControl %s", + *furi_hal_version_get_ble_local_device_name_ptr(), + furi_hal_version_get_ble_local_device_name_ptr() + 9); + } } if(!gap_init(config, event_cb, context)) { gap_thread_stop(); @@ -280,6 +293,10 @@ bool furi_hal_bt_is_active() { return gap_get_state() > GapStateIdle; } +bool furi_hal_bt_is_connected() { + return gap_get_state() == GapStateConnected; +} + void furi_hal_bt_start_advertising() { if(gap_get_state() == GapStateIdle) { gap_start_advertising(); @@ -418,6 +435,67 @@ float furi_hal_bt_get_rssi() { return val; } +/** fill the RSSI of the remote host of the bt connection and returns the last + * time the RSSI was updated + * +*/ +uint32_t furi_hal_bt_get_conn_rssi(uint8_t* rssi) { + int8_t ret_rssi = 0; + uint32_t since = gap_get_remote_conn_rssi(&ret_rssi); + + if(ret_rssi == 127 || since == 0) return 0; + + *rssi = (uint8_t)abs(ret_rssi); + + return since; +} + +void furi_hal_bt_set_profile_adv_name( + FuriHalBtProfile profile, + const char name[FURI_HAL_BT_ADV_NAME_LENGTH]) { + furi_assert(profile < FuriHalBtProfileNumber); + furi_assert(name); + + if(strlen(name) == 0) { + memset( + &(profile_config[profile].config.adv_name[1]), + 0, + strlen(&(profile_config[profile].config.adv_name[1]))); + } else { + profile_config[profile].config.adv_name[0] = AD_TYPE_COMPLETE_LOCAL_NAME; + memcpy(&(profile_config[profile].config.adv_name[1]), name, FURI_HAL_BT_ADV_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; +} + +void furi_hal_bt_set_profile_pairing_method(FuriHalBtProfile profile, GapPairing pairing_method) { + furi_assert(profile < FuriHalBtProfileNumber); + profile_config[profile].config.pairing_method = pairing_method; +} + +GapPairing furi_hal_bt_get_profile_pairing_method(FuriHalBtProfile profile) { + furi_assert(profile < FuriHalBtProfileNumber); + return profile_config[profile].config.pairing_method; +} + uint32_t furi_hal_bt_get_transmitted_packets() { uint32_t packets = 0; aci_hal_le_tx_test_packet_number(&packets); 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 8259be2f6..860edfcd4 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_bt_hid.c +++ b/firmware/targets/f7/furi_hal/furi_hal_bt_hid.c @@ -20,6 +20,7 @@ enum HidReportId { ReportIdKeyboard = 1, ReportIdMouse = 2, ReportIdConsumer = 3, + ReportIdLEDState = 4, }; // Report numbers corresponded to the report id with an offset of 1 enum HidInputNumber { @@ -77,6 +78,13 @@ static const uint8_t furi_hal_bt_hid_report_map_data[] = { HID_USAGE_MINIMUM(0), HID_USAGE_MAXIMUM(101), HID_INPUT(HID_IOF_DATA | HID_IOF_ARRAY | HID_IOF_ABSOLUTE), + HID_REPORT_ID(ReportIdLEDState), + HID_USAGE_PAGE(HID_PAGE_LED), + HID_REPORT_COUNT(8), + HID_REPORT_SIZE(1), + HID_USAGE_MINIMUM(1), + HID_USAGE_MAXIMUM(8), + HID_OUTPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), HID_END_COLLECTION, // Mouse Report HID_USAGE_PAGE(HID_PAGE_DESKTOP), @@ -125,6 +133,63 @@ FuriHalBtHidKbReport* kb_report = NULL; FuriHalBtHidMouseReport* mouse_report = NULL; FuriHalBtHidConsumerReport* consumer_report = NULL; +typedef struct { +// shortcuts +#define s_undefined data.bits.b_undefined +#define s_num_lock data.bits.b_num_lock +#define s_caps_lock data.bits.b_caps_lock +#define s_scroll_lock data.bits.b_scroll_lock +#define s_compose data.bits.b_compose +#define s_kana data.bits.b_kana +#define s_power data.bits.b_power +#define s_shift data.bits.b_shift +#define s_value data.value + union { + struct { + uint8_t b_undefined : 1; + uint8_t b_num_lock : 1; + uint8_t b_caps_lock : 1; + uint8_t b_scroll_lock : 1; + uint8_t b_compose : 1; + uint8_t b_kana : 1; + uint8_t b_power : 1; + uint8_t b_shift : 1; + } bits; + uint8_t value; + } data; +} __attribute__((__packed__)) FuriHalBtHidLedState; + +FuriHalBtHidLedState hid_host_led_state = {.s_value = 0}; + +uint16_t furi_hal_bt_hid_led_state_cb(uint8_t state, void* ctx) { + FuriHalBtHidLedState* led_state = (FuriHalBtHidLedState*)ctx; + + //FURI_LOG_D("HalBtHid", "LED state updated !"); + + led_state->s_value = state; + + return 0; +} + +uint8_t furi_hal_bt_hid_get_led_state(void) { + /*FURI_LOG_D( + "HalBtHid", + "LED state: RFU=%d NUMLOCK=%d CAPSLOCK=%d SCROLLLOCK=%d COMPOSE=%d KANA=%d POWER=%d SHIFT=%d", + hid_host_led_state.s_undefined, + hid_host_led_state.s_num_lock, + hid_host_led_state.s_caps_lock, + hid_host_led_state.s_scroll_lock, + hid_host_led_state.s_compose, + hid_host_led_state.s_kana, + hid_host_led_state.s_power, + hid_host_led_state.s_shift); + */ + + return (hid_host_led_state.s_value >> 1); // bit 0 is undefined (after shift bit location + // match with HID led state bits defines) + // see bad_bt_script.c (ducky_numlock_on function) +} + void furi_hal_bt_hid_start() { // Start device info if(!dev_info_svc_is_started()) { @@ -139,6 +204,8 @@ void furi_hal_bt_hid_start() { hid_svc_start(); } // Configure HID Keyboard + hid_svc_register_led_state_callback(furi_hal_bt_hid_led_state_cb, &hid_host_led_state); + kb_report = malloc(sizeof(FuriHalBtHidKbReport)); mouse_report = malloc(sizeof(FuriHalBtHidMouseReport)); consumer_report = malloc(sizeof(FuriHalBtHidConsumerReport)); @@ -160,6 +227,8 @@ void furi_hal_bt_hid_stop() { furi_assert(kb_report); furi_assert(mouse_report); furi_assert(consumer_report); + + hid_svc_register_led_state_callback(NULL, NULL); // Stop all services if(dev_info_svc_is_started()) { dev_info_svc_stop(); @@ -180,12 +249,16 @@ 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)); diff --git a/firmware/targets/furi_hal_include/furi_hal_bt.h b/firmware/targets/furi_hal_include/furi_hal_bt.h index 6ba38cb5e..f128b1064 100644 --- a/firmware/targets/furi_hal_include/furi_hal_bt.h +++ b/firmware/targets/furi_hal_include/furi_hal_bt.h @@ -218,6 +218,35 @@ float furi_hal_bt_get_rssi(); */ uint32_t furi_hal_bt_get_transmitted_packets(); +// BadBT stuff +/** Modify profile advertisement name and restart bluetooth + * @param[in] profile profile type + * @param[in] name new adv name +*/ +void furi_hal_bt_set_profile_adv_name( + FuriHalBtProfile profile, + const char name[FURI_HAL_BT_ADV_NAME_LENGTH]); + +const char* furi_hal_bt_get_profile_adv_name(FuriHalBtProfile profile); + +/** Modify profile mac address and restart bluetooth + * @param[in] profile profile type + * @param[in] mac new mac address +*/ +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); + +uint32_t furi_hal_bt_get_conn_rssi(uint8_t* rssi); + +void furi_hal_bt_set_profile_pairing_method(FuriHalBtProfile profile, GapPairing pairing_method); + +GapPairing furi_hal_bt_get_profile_pairing_method(FuriHalBtProfile profile); + +bool furi_hal_bt_is_connected(void); + /** Check & switch C2 to given mode * * @param[in] mode mode to switch into 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..56a8b4e48 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,13 @@ bool furi_hal_bt_hid_consumer_key_release(uint16_t button); */ bool furi_hal_bt_hid_consumer_key_release_all(); +/** Retrieves LED state from remote BT HID host + * + * @return (look at HID usage page to know what each bit of the returned byte means) + * NB: RFU bit has been shifted out in the returned octet so USB defines should work +*/ +uint8_t furi_hal_bt_hid_get_led_state(void); + #ifdef __cplusplus } #endif diff --git a/firmware/targets/furi_hal_include/furi_hal_version.h b/firmware/targets/furi_hal_include/furi_hal_version.h index bfe691aeb..6a3e3154d 100644 --- a/firmware/targets/furi_hal_include/furi_hal_version.h +++ b/firmware/targets/furi_hal_include/furi_hal_version.h @@ -16,6 +16,7 @@ extern "C" { #define FURI_HAL_VERSION_NAME_LENGTH 8 #define FURI_HAL_VERSION_ARRAY_NAME_LENGTH (FURI_HAL_VERSION_NAME_LENGTH + 1) +#define FURI_HAL_BT_ADV_NAME_LENGTH (18 + 1) // 18 characters + null terminator /** BLE symbol + "Flipper " + name */ #define FURI_HAL_VERSION_DEVICE_NAME_LENGTH (1 + 8 + FURI_HAL_VERSION_ARRAY_NAME_LENGTH)