Bad BT plugin, Submenu locked elements, API updates, etc.

Thanks to WillyJL, ClaraCrazy, and XFW contributors
This commit is contained in:
MX 2023-05-13 00:14:22 +03:00
parent a7691b2d3b
commit 849f14e480
No known key found for this signature in database
GPG key ID: 7CCC66B7DBDD1C83
42 changed files with 3211 additions and 44 deletions

View file

@ -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",
)

View file

@ -0,0 +1,333 @@
#include "bad_bt_app.h"
#include <furi.h>
#include <furi_hal.h>
#include <storage/storage.h>
#include <lib/toolbox/path.h>
#include <lib/flipper_format/flipper_format.h>
#include <bt/bt_service/bt_i.h>
#include <bt/bt_service/bt.h>
#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;
}

View file

@ -0,0 +1,39 @@
#pragma once
#include "scenes/bad_bt_scene.h"
#include "helpers/ducky_script.h"
#include <gui/gui.h>
#include <assets_icons.h>
#include <gui/scene_manager.h>
#include <dialogs/dialogs.h>
#include <notification/notification_messages.h>
#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);

View file

@ -0,0 +1,777 @@
#include <furi.h>
#include <furi_hal.h>
#include <gui/gui.h>
#include <input/input.h>
#include <lib/toolbox/args.h>
#include <furi_hal_bt_hid.h>
#include <bt/bt_service/bt.h>
#include <storage/storage.h>
#include "ducky_script.h"
#include "ducky_script_i.h"
#include <dolphin/dolphin.h>
#include <toolbox/hex.h>
#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);
}

View file

@ -0,0 +1,154 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <furi.h>
#include <furi_hal.h>
#include <bt/bt_service/bt_i.h>
#include <gui/view_dispatcher.h>
#include <gui/modules/widget.h>
#include <gui/modules/variable_item_list.h>
#include <gui/modules/text_input.h>
#include <gui/modules/byte_input.h>
#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

View file

@ -0,0 +1,201 @@
#include <furi_hal.h>
#include <furi_hal_bt_hid.h>
#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;
}

View file

@ -0,0 +1,44 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <furi.h>
#include <furi_hal.h>
#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

View file

@ -0,0 +1,78 @@
#include <furi_hal.h>
#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;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 576 B

View file

@ -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,
};

View file

@ -0,0 +1,29 @@
#pragma once
#include <gui/scene_manager.h>
// 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

View file

@ -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);
}

View file

@ -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)

View file

@ -0,0 +1,47 @@
#include "../bad_bt_app.h"
#include "furi_hal_power.h"
#include <storage/storage.h>
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);
}

View file

@ -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, "");
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -0,0 +1,49 @@
#include "../bad_bt_app.h"
#include <furi_hal_power.h>
#include <storage/storage.h>
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;
}

View file

@ -0,0 +1,56 @@
#include "../helpers/ducky_script.h"
#include "../bad_bt_app.h"
#include "../views/bad_bt_view.h"
#include <furi_hal.h>
#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);
}

View file

@ -0,0 +1,233 @@
#include "bad_bt_view.h"
#include "../helpers/ducky_script.h"
#include "../bad_bt_app.h"
#include <toolbox/path.h>
#include <gui/elements.h>
#include <assets_icons.h>
#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;
}

View file

@ -0,0 +1,29 @@
#pragma once
#include <gui/view.h>
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);

View file

@ -11,4 +11,4 @@
// End of list
// Target firmware to build for
#define TOTP_TARGET_FIRMWARE TOTP_FIRMWARE_OFFICIAL_DEV
#define TOTP_TARGET_FIRMWARE TOTP_FIRMWARE_XTREME

View file

@ -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();

View file

@ -3,6 +3,8 @@
#include <stdint.h>
#include <stdbool.h>
#include <furi_hal_bt.h>
#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

View file

@ -76,4 +76,6 @@ struct Bt {
FuriEventFlag* api_event;
BtStatusChangedCallback status_changed_cb;
void* status_changed_ctx;
uint32_t pin;
bool suppress_pin_screen;
};

View file

@ -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,

View file

@ -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

View file

@ -1,11 +1,14 @@
#include "submenu.h"
#include <gui/canvas_i.h>
#include <assets_icons.h>
#include <gui/elements.h>
#include <furi.h>
#include <m-array.h>
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);
}
}

View file

@ -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

View file

@ -2,6 +2,7 @@
#include <gui/elements.h>
#include <gui/canvas.h>
#include <furi.h>
#include <assets_icons.h>
#include <m-array.h>
#include <stdint.h>
@ -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;
}

View file

@ -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

View file

@ -1,5 +1,6 @@
#include "../bt_settings_app.h"
#include <furi_hal_bt.h>
#include <storage/storage.h>
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;
}

View file

@ -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"

1 entry status name type params
586 Function + ble_glue_thread_stop void
587 Function + ble_glue_wait_for_c2_start _Bool int32_t
588 Function - bsearch void* const void*, const void*, size_t, size_t, __compar_fn_t
589 Function + bt_disable_peer_key_update void Bt*
590 Function + bt_disconnect void Bt*
591 Function + bt_enable_peer_key_update void Bt*
592 Function + bt_forget_bonded_devices void Bt*
593 Function + bt_get_profile_adv_name const char* Bt*
594 Function + bt_get_profile_mac_address const uint8_t* Bt*
595 Function + bt_get_profile_pairing_method GapPairing Bt*
596 Function + bt_keys_storage_set_default_path void Bt*
597 Function + bt_keys_storage_set_storage_path void Bt*, const char*
598 Function + bt_remote_rssi _Bool Bt*, uint8_t*
599 Function + bt_set_profile _Bool Bt*, BtProfile
600 Function + bt_set_profile_adv_name void Bt*, const char*, ...
601 Function + bt_set_profile_mac_address void Bt*, const uint8_t[6]
602 Function + bt_set_profile_pairing_method void Bt*, GapPairing
603 Function + bt_set_status_changed_callback void Bt*, BtStatusChangedCallback, void*
604 Function + buffered_file_stream_alloc Stream* Storage*
605 Function + buffered_file_stream_close _Bool Stream*
825 Function + elements_progress_bar void Canvas*, uint8_t, uint8_t, uint8_t, float
826 Function + elements_progress_bar_with_text void Canvas*, uint8_t, uint8_t, uint8_t, float, const char*
827 Function + elements_scrollable_text_line void Canvas*, uint8_t, uint8_t, uint8_t, FuriString*, size_t, _Bool
828 Function + elements_scrollable_text_line_str void Canvas*, uint8_t, uint8_t, uint8_t, const char*, size_t, _Bool, _Bool
829 Function + elements_scrollbar void Canvas*, uint16_t, uint16_t
830 Function + elements_scrollbar_pos void Canvas*, uint8_t, uint8_t, uint8_t, uint16_t, uint16_t
831 Function + elements_slightly_rounded_box void Canvas*, uint8_t, uint8_t, uint8_t, uint8_t
1047 Function + furi_hal_bt_clear_white_list _Bool
1048 Function + furi_hal_bt_dump_state void FuriString*
1049 Function + furi_hal_bt_ensure_c2_mode _Bool BleGlueC2Mode
1050 Function + furi_hal_bt_get_conn_rssi uint32_t uint8_t*
1051 Function - furi_hal_bt_get_hardfault_info const FuriHalBtHardfaultInfo*
1052 Function + furi_hal_bt_get_key_storage_buff void uint8_t**, uint16_t*
1053 Function + furi_hal_bt_get_profile_adv_name const char* FuriHalBtProfile
1054 Function + furi_hal_bt_get_profile_mac_addr const uint8_t* FuriHalBtProfile
1055 Function + furi_hal_bt_get_profile_pairing_method GapPairing FuriHalBtProfile
1056 Function + furi_hal_bt_get_radio_stack FuriHalBtStack
1057 Function + furi_hal_bt_get_rssi float
1058 Function + furi_hal_bt_get_transmitted_packets uint32_t
1059 Function + furi_hal_bt_hid_consumer_key_press _Bool uint16_t
1060 Function + furi_hal_bt_hid_consumer_key_release _Bool uint16_t
1061 Function + furi_hal_bt_hid_consumer_key_release_all _Bool
1062 Function + furi_hal_bt_hid_get_led_state uint8_t
1063 Function + furi_hal_bt_hid_kb_press _Bool uint16_t
1064 Function + furi_hal_bt_hid_kb_release _Bool uint16_t
1065 Function + furi_hal_bt_hid_kb_release_all _Bool
1074 Function + furi_hal_bt_is_active _Bool
1075 Function + furi_hal_bt_is_alive _Bool
1076 Function + furi_hal_bt_is_ble_gatt_gap_supported _Bool
1077 Function + furi_hal_bt_is_connected _Bool
1078 Function + furi_hal_bt_is_testing_supported _Bool
1079 Function + furi_hal_bt_lock_core2 void
1080 Function + furi_hal_bt_nvm_sram_sem_acquire void
1087 Function + furi_hal_bt_serial_stop void
1088 Function + furi_hal_bt_serial_tx _Bool uint8_t*, uint16_t
1089 Function + furi_hal_bt_set_key_storage_change_callback void BleGlueKeyStorageChangedCallback, void*
1090 Function + furi_hal_bt_set_profile_adv_name void FuriHalBtProfile, const char[( 18 + 1 )]
1091 Function + furi_hal_bt_set_profile_mac_addr void FuriHalBtProfile, const uint8_t[( 6 )]
1092 Function + furi_hal_bt_set_profile_pairing_method void FuriHalBtProfile, GapPairing
1093 Function + furi_hal_bt_start_advertising void
1094 Function + furi_hal_bt_start_app _Bool FuriHalBtProfile, GapEventCallback, void*
1095 Function + furi_hal_bt_start_packet_rx void uint8_t, uint8_t
1652 Function - gamma_r double double, int*
1653 Function - gammaf float float
1654 Function - gammaf_r float float, int*
1655 Function + gap_get_remote_conn_rssi uint32_t int8_t*
1656 Function - gap_get_state GapState
1657 Function - gap_init _Bool GapConfig*, GapEventCallback, void*
1658 Function - gap_start_advertising void
3354 Function + subghz_worker_start void SubGhzWorker*
3355 Function + subghz_worker_stop void SubGhzWorker*
3356 Function + submenu_add_item void Submenu*, const char*, uint32_t, SubmenuItemCallback, void*
3357 Function + submenu_add_lockable_item void Submenu*, const char*, uint32_t, SubmenuItemCallback, void*, _Bool, const char*
3358 Function + submenu_alloc Submenu*
3359 Function + submenu_free void Submenu*
3360 Function + submenu_get_view View* Submenu*
4578 Function + variable_item_list_set_selected_item void VariableItemList*, uint8_t
4579 Function + variable_item_set_current_value_index void VariableItem*, uint8_t
4580 Function + variable_item_set_current_value_text void VariableItem*, const char*
4581 Function + variable_item_set_locked void VariableItem*, _Bool, const char*
4582 Function + variable_item_set_values_count void VariableItem*, uint8_t
4583 Function - vasiprintf int char**, const char*, __gnuc_va_list
4584 Function - vasniprintf char* char*, size_t*, const char*, __gnuc_va_list

View file

@ -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) {

View file

@ -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

View file

@ -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) {

View file

@ -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);

View file

@ -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);

View file

@ -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));

View file

@ -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

View file

@ -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

View file

@ -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)