mirror of
https://github.com/DarkFlippers/unleashed-firmware
synced 2024-11-23 04:53:08 +00:00
Bad BT plugin, Submenu locked elements, API updates, etc.
Thanks to WillyJL, ClaraCrazy, and XFW contributors
This commit is contained in:
parent
a7691b2d3b
commit
849f14e480
42 changed files with 3211 additions and 44 deletions
17
applications/external/bad_bt/application.fam
vendored
Normal file
17
applications/external/bad_bt/application.fam
vendored
Normal 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",
|
||||
)
|
333
applications/external/bad_bt/bad_bt_app.c
vendored
Normal file
333
applications/external/bad_bt/bad_bt_app.c
vendored
Normal 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;
|
||||
}
|
39
applications/external/bad_bt/bad_bt_app.h
vendored
Normal file
39
applications/external/bad_bt/bad_bt_app.h
vendored
Normal 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);
|
777
applications/external/bad_bt/helpers/ducky_script.c
vendored
Normal file
777
applications/external/bad_bt/helpers/ducky_script.c
vendored
Normal 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);
|
||||
}
|
154
applications/external/bad_bt/helpers/ducky_script.h
vendored
Normal file
154
applications/external/bad_bt/helpers/ducky_script.h
vendored
Normal 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
|
201
applications/external/bad_bt/helpers/ducky_script_commands.c
vendored
Normal file
201
applications/external/bad_bt/helpers/ducky_script_commands.c
vendored
Normal 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;
|
||||
}
|
44
applications/external/bad_bt/helpers/ducky_script_i.h
vendored
Normal file
44
applications/external/bad_bt/helpers/ducky_script_i.h
vendored
Normal 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
|
78
applications/external/bad_bt/helpers/ducky_script_keycodes.c
vendored
Normal file
78
applications/external/bad_bt/helpers/ducky_script_keycodes.c
vendored
Normal 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;
|
||||
}
|
BIN
applications/external/bad_bt/images/badbt_10px.png
vendored
Normal file
BIN
applications/external/bad_bt/images/badbt_10px.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 576 B |
30
applications/external/bad_bt/scenes/bad_bt_scene.c
vendored
Normal file
30
applications/external/bad_bt/scenes/bad_bt_scene.c
vendored
Normal 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,
|
||||
};
|
29
applications/external/bad_bt/scenes/bad_bt_scene.h
vendored
Normal file
29
applications/external/bad_bt/scenes/bad_bt_scene.h
vendored
Normal 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
|
104
applications/external/bad_bt/scenes/bad_bt_scene_config.c
vendored
Normal file
104
applications/external/bad_bt/scenes/bad_bt_scene_config.c
vendored
Normal 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);
|
||||
}
|
7
applications/external/bad_bt/scenes/bad_bt_scene_config.h
vendored
Normal file
7
applications/external/bad_bt/scenes/bad_bt_scene_config.h
vendored
Normal 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)
|
47
applications/external/bad_bt/scenes/bad_bt_scene_config_layout.c
vendored
Normal file
47
applications/external/bad_bt/scenes/bad_bt_scene_config_layout.c
vendored
Normal 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);
|
||||
}
|
47
applications/external/bad_bt/scenes/bad_bt_scene_config_mac.c
vendored
Normal file
47
applications/external/bad_bt/scenes/bad_bt_scene_config_mac.c
vendored
Normal 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, "");
|
||||
}
|
45
applications/external/bad_bt/scenes/bad_bt_scene_config_name.c
vendored
Normal file
45
applications/external/bad_bt/scenes/bad_bt_scene_config_name.c
vendored
Normal 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);
|
||||
}
|
61
applications/external/bad_bt/scenes/bad_bt_scene_error.c
vendored
Normal file
61
applications/external/bad_bt/scenes/bad_bt_scene_error.c
vendored
Normal 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);
|
||||
}
|
49
applications/external/bad_bt/scenes/bad_bt_scene_file_select.c
vendored
Normal file
49
applications/external/bad_bt/scenes/bad_bt_scene_file_select.c
vendored
Normal 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;
|
||||
}
|
56
applications/external/bad_bt/scenes/bad_bt_scene_work.c
vendored
Normal file
56
applications/external/bad_bt/scenes/bad_bt_scene_work.c
vendored
Normal 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);
|
||||
}
|
233
applications/external/bad_bt/views/bad_bt_view.c
vendored
Normal file
233
applications/external/bad_bt/views/bad_bt_view.c
vendored
Normal 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;
|
||||
}
|
29
applications/external/bad_bt/views/bad_bt_view.h
vendored
Normal file
29
applications/external/bad_bt/views/bad_bt_view.h
vendored
Normal 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);
|
2
applications/external/totp/features_config.h
vendored
2
applications/external/totp/features_config.h
vendored
|
@ -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
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -76,4 +76,6 @@ struct Bt {
|
|||
FuriEventFlag* api_event;
|
||||
BtStatusChangedCallback status_changed_cb;
|
||||
void* status_changed_ctx;
|
||||
uint32_t pin;
|
||||
bool suppress_pin_screen;
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
Loading…
Reference in a new issue