Adds basic bad BLE app (cannot modify adv name yet, cannot efficiently send keys with harcoded timeouts between press/release)
18
applications/main/bad_ble/application.fam
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
App(
|
||||||
|
appid="bad_ble",
|
||||||
|
name="Bad BLE",
|
||||||
|
apptype=FlipperAppType.EXTERNAL,
|
||||||
|
entry_point="bad_ble_app",
|
||||||
|
cdefines=["APP_BAD_BLE"],
|
||||||
|
requires=[
|
||||||
|
"gui",
|
||||||
|
"dialogs",
|
||||||
|
],
|
||||||
|
stack_size=2 * 1024,
|
||||||
|
# icon="A_BadUsb_14",
|
||||||
|
order=70,
|
||||||
|
fap_category="Main",
|
||||||
|
fap_icon="badusb_10px.png",
|
||||||
|
fap_icon_assets="images",
|
||||||
|
fap_libs=["assets"],
|
||||||
|
)
|
||||||
161
applications/main/bad_ble/bad_ble_app.c
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
#include "bad_ble_app_i.h"
|
||||||
|
#include "bad_ble_settings_filename.h"
|
||||||
|
#include <furi.h>
|
||||||
|
#include <furi_hal.h>
|
||||||
|
#include <storage/storage.h>
|
||||||
|
#include <lib/toolbox/path.h>
|
||||||
|
|
||||||
|
#include <bt/bt_service/bt_i.h>
|
||||||
|
#include <bt/bt_service/bt.h>
|
||||||
|
|
||||||
|
#define BAD_BLE_SETTINGS_PATH BAD_BLE_APP_BASE_FOLDER "/" BAD_BLE_SETTINGS_FILE_NAME
|
||||||
|
|
||||||
|
static bool bad_ble_app_custom_event_callback(void* context, uint32_t event) {
|
||||||
|
furi_assert(context);
|
||||||
|
BadBleApp* app = context;
|
||||||
|
return scene_manager_handle_custom_event(app->scene_manager, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool bad_ble_app_back_event_callback(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
BadBleApp* app = context;
|
||||||
|
return scene_manager_handle_back_event(app->scene_manager);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void bad_ble_app_tick_event_callback(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
BadBleApp* app = context;
|
||||||
|
scene_manager_handle_tick_event(app->scene_manager);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void bad_ble_load_settings(BadBleApp* app) {
|
||||||
|
File* settings_file = storage_file_alloc(furi_record_open(RECORD_STORAGE));
|
||||||
|
if(storage_file_open(settings_file, BAD_BLE_SETTINGS_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) {
|
||||||
|
char chr;
|
||||||
|
while((storage_file_read(settings_file, &chr, 1) == 1) &&
|
||||||
|
!storage_file_eof(settings_file) && !isspace(chr)) {
|
||||||
|
furi_string_push_back(app->keyboard_layout, chr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
storage_file_close(settings_file);
|
||||||
|
storage_file_free(settings_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void bad_ble_save_settings(BadBleApp* app) {
|
||||||
|
File* settings_file = storage_file_alloc(furi_record_open(RECORD_STORAGE));
|
||||||
|
if(storage_file_open(settings_file, BAD_BLE_SETTINGS_PATH, FSAM_WRITE, FSOM_OPEN_ALWAYS)) {
|
||||||
|
storage_file_write(
|
||||||
|
settings_file,
|
||||||
|
furi_string_get_cstr(app->keyboard_layout),
|
||||||
|
furi_string_size(app->keyboard_layout));
|
||||||
|
storage_file_write(settings_file, "\n", 1);
|
||||||
|
}
|
||||||
|
storage_file_close(settings_file);
|
||||||
|
storage_file_free(settings_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
BadBleApp* bad_ble_app_alloc(char* arg) {
|
||||||
|
BadBleApp* app = malloc(sizeof(BadBleApp));
|
||||||
|
|
||||||
|
app->bad_ble_script = NULL;
|
||||||
|
|
||||||
|
app->file_path = furi_string_alloc();
|
||||||
|
app->keyboard_layout = furi_string_alloc();
|
||||||
|
if(arg && strlen(arg)) {
|
||||||
|
furi_string_set(app->file_path, arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
bad_ble_load_settings(app);
|
||||||
|
|
||||||
|
app->gui = furi_record_open(RECORD_GUI);
|
||||||
|
app->notifications = furi_record_open(RECORD_NOTIFICATION);
|
||||||
|
app->dialogs = furi_record_open(RECORD_DIALOGS);
|
||||||
|
|
||||||
|
app->view_dispatcher = view_dispatcher_alloc();
|
||||||
|
view_dispatcher_enable_queue(app->view_dispatcher);
|
||||||
|
|
||||||
|
app->scene_manager = scene_manager_alloc(&bad_ble_scene_handlers, app);
|
||||||
|
|
||||||
|
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
||||||
|
view_dispatcher_set_tick_event_callback(
|
||||||
|
app->view_dispatcher, bad_ble_app_tick_event_callback, 500);
|
||||||
|
view_dispatcher_set_custom_event_callback(
|
||||||
|
app->view_dispatcher, bad_ble_app_custom_event_callback);
|
||||||
|
view_dispatcher_set_navigation_event_callback(
|
||||||
|
app->view_dispatcher, bad_ble_app_back_event_callback);
|
||||||
|
|
||||||
|
// Custom Widget
|
||||||
|
app->widget = widget_alloc();
|
||||||
|
view_dispatcher_add_view(
|
||||||
|
app->view_dispatcher, BadBleAppViewError, widget_get_view(app->widget));
|
||||||
|
|
||||||
|
app->submenu = submenu_alloc();
|
||||||
|
view_dispatcher_add_view(
|
||||||
|
app->view_dispatcher, BadBleAppViewConfig, submenu_get_view(app->submenu));
|
||||||
|
|
||||||
|
app->bad_ble_view = bad_ble_alloc();
|
||||||
|
view_dispatcher_add_view(
|
||||||
|
app->view_dispatcher, BadBleAppViewWork, bad_ble_get_view(app->bad_ble_view));
|
||||||
|
|
||||||
|
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||||
|
|
||||||
|
Bt* bt = furi_record_open(RECORD_BT);
|
||||||
|
app->bt = bt;
|
||||||
|
if(!furi_string_empty(app->file_path)) {
|
||||||
|
app->bad_ble_script = bad_ble_script_open(app->file_path, bt);
|
||||||
|
bad_ble_script_set_keyboard_layout(app->bad_ble_script, app->keyboard_layout);
|
||||||
|
scene_manager_next_scene(app->scene_manager, BadBleSceneWork);
|
||||||
|
} else {
|
||||||
|
furi_string_set(app->file_path, BAD_BLE_APP_BASE_FOLDER);
|
||||||
|
scene_manager_next_scene(app->scene_manager, BadBleSceneFileSelect);
|
||||||
|
}
|
||||||
|
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
|
||||||
|
void bad_ble_app_free(BadBleApp* app) {
|
||||||
|
furi_assert(app);
|
||||||
|
|
||||||
|
if(app->bad_ble_script) {
|
||||||
|
bad_ble_script_close(app->bad_ble_script);
|
||||||
|
app->bad_ble_script = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Views
|
||||||
|
view_dispatcher_remove_view(app->view_dispatcher, BadBleAppViewWork);
|
||||||
|
bad_ble_free(app->bad_ble_view);
|
||||||
|
|
||||||
|
// Custom Widget
|
||||||
|
view_dispatcher_remove_view(app->view_dispatcher, BadBleAppViewError);
|
||||||
|
widget_free(app->widget);
|
||||||
|
|
||||||
|
// Submenu
|
||||||
|
view_dispatcher_remove_view(app->view_dispatcher, BadBleAppViewConfig);
|
||||||
|
submenu_free(app->submenu);
|
||||||
|
|
||||||
|
// View dispatcher
|
||||||
|
view_dispatcher_free(app->view_dispatcher);
|
||||||
|
scene_manager_free(app->scene_manager);
|
||||||
|
|
||||||
|
// Close records
|
||||||
|
furi_record_close(RECORD_GUI);
|
||||||
|
furi_record_close(RECORD_NOTIFICATION);
|
||||||
|
furi_record_close(RECORD_DIALOGS);
|
||||||
|
furi_record_close(RECORD_BT);
|
||||||
|
|
||||||
|
bad_ble_save_settings(app);
|
||||||
|
|
||||||
|
furi_string_free(app->file_path);
|
||||||
|
furi_string_free(app->keyboard_layout);
|
||||||
|
|
||||||
|
free(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t bad_ble_app(void* p) {
|
||||||
|
BadBleApp* bad_ble_app = bad_ble_app_alloc((char*)p);
|
||||||
|
|
||||||
|
view_dispatcher_run(bad_ble_app->view_dispatcher);
|
||||||
|
|
||||||
|
bad_ble_app_free(bad_ble_app);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
11
applications/main/bad_ble/bad_ble_app.h
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct BadBleApp BadBleApp;
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
50
applications/main/bad_ble/bad_ble_app_i.h
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "bad_ble_app.h"
|
||||||
|
#include "scenes/bad_ble_scene.h"
|
||||||
|
#include "bad_ble_script.h"
|
||||||
|
|
||||||
|
#include <gui/gui.h>
|
||||||
|
#include <bad_ble_icons.h>
|
||||||
|
#include <gui/view_dispatcher.h>
|
||||||
|
#include <gui/scene_manager.h>
|
||||||
|
#include <gui/modules/submenu.h>
|
||||||
|
#include <dialogs/dialogs.h>
|
||||||
|
#include <notification/notification_messages.h>
|
||||||
|
#include <gui/modules/variable_item_list.h>
|
||||||
|
#include <gui/modules/widget.h>
|
||||||
|
#include "views/bad_ble_view.h"
|
||||||
|
|
||||||
|
#define BAD_BLE_APP_BASE_FOLDER ANY_PATH("BadUsb")
|
||||||
|
#define BAD_BLE_APP_PATH_LAYOUT_FOLDER BAD_BLE_APP_BASE_FOLDER "/layouts"
|
||||||
|
#define BAD_BLE_APP_SCRIPT_EXTENSION ".txt"
|
||||||
|
#define BAD_BLE_APP_LAYOUT_EXTENSION ".kl"
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
BadBleAppErrorNoFiles,
|
||||||
|
BadBleAppErrorCloseRpc,
|
||||||
|
} BadBleAppError;
|
||||||
|
|
||||||
|
struct BadBleApp {
|
||||||
|
Gui* gui;
|
||||||
|
ViewDispatcher* view_dispatcher;
|
||||||
|
SceneManager* scene_manager;
|
||||||
|
NotificationApp* notifications;
|
||||||
|
DialogsApp* dialogs;
|
||||||
|
Widget* widget;
|
||||||
|
Submenu* submenu;
|
||||||
|
|
||||||
|
BadBleAppError error;
|
||||||
|
FuriString* file_path;
|
||||||
|
FuriString* keyboard_layout;
|
||||||
|
BadBle* bad_ble_view;
|
||||||
|
BadBleScript* bad_ble_script;
|
||||||
|
|
||||||
|
Bt* bt;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
BadBleAppViewError,
|
||||||
|
BadBleAppViewWork,
|
||||||
|
BadBleAppViewConfig,
|
||||||
|
} BadBleAppView;
|
||||||
783
applications/main/bad_ble/bad_ble_script.c
Normal file
@@ -0,0 +1,783 @@
|
|||||||
|
#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 <storage/storage.h>
|
||||||
|
#include "bad_ble_script.h"
|
||||||
|
#include <dolphin/dolphin.h>
|
||||||
|
|
||||||
|
#include <bt/bt_service/bt.h>
|
||||||
|
|
||||||
|
#define HID_BT_KEYS_STORAGE_PATH EXT_PATH("apps/Tools/.bt_hid.keys")
|
||||||
|
|
||||||
|
#define TAG "BadBle"
|
||||||
|
#define WORKER_TAG TAG "Worker"
|
||||||
|
#define FILE_BUFFER_LEN 16
|
||||||
|
|
||||||
|
#define SCRIPT_STATE_ERROR (-1)
|
||||||
|
#define SCRIPT_STATE_END (-2)
|
||||||
|
#define SCRIPT_STATE_NEXT_LINE (-3)
|
||||||
|
|
||||||
|
#define BADBLE_ASCII_TO_KEY(script, x) \
|
||||||
|
(((uint8_t)x < 128) ? (script->layout[(uint8_t)x]) : HID_KEYBOARD_NONE)
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
WorkerEvtToggle = (1 << 0),
|
||||||
|
WorkerEvtEnd = (1 << 1),
|
||||||
|
WorkerEvtConnect = (1 << 2),
|
||||||
|
WorkerEvtDisconnect = (1 << 3),
|
||||||
|
} WorkerEvtFlags;
|
||||||
|
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
LevelRssi122_100,
|
||||||
|
LevelRssi99_80,
|
||||||
|
LevelRssi79_60,
|
||||||
|
LevelRssi59_40,
|
||||||
|
LevelRssi39_0,
|
||||||
|
LevelRssiNum,
|
||||||
|
} LevelRssiDelays;
|
||||||
|
|
||||||
|
const uint8_t bt_hid_delays[LevelRssiNum] = {
|
||||||
|
75, // LevelRssi122_100
|
||||||
|
50, // LevelRssi99_80
|
||||||
|
35, // LevelRssi79_60
|
||||||
|
25, // LevelRssi59_40
|
||||||
|
10, // LevelRssi39_0
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BadBleScript {
|
||||||
|
BadBleState st;
|
||||||
|
FuriString* file_path;
|
||||||
|
uint32_t defdelay;
|
||||||
|
uint16_t layout[128];
|
||||||
|
FuriThread* thread;
|
||||||
|
uint8_t file_buf[FILE_BUFFER_LEN + 1];
|
||||||
|
uint8_t buf_start;
|
||||||
|
uint8_t buf_len;
|
||||||
|
bool file_end;
|
||||||
|
FuriString* line;
|
||||||
|
|
||||||
|
FuriString* line_prev;
|
||||||
|
uint32_t repeat_cnt;
|
||||||
|
|
||||||
|
File* debug_file;
|
||||||
|
Bt* bt;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char* name;
|
||||||
|
uint16_t keycode;
|
||||||
|
} DuckyKey;
|
||||||
|
|
||||||
|
static const DuckyKey ducky_keys[] = {
|
||||||
|
{"CTRL-ALT", KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_ALT},
|
||||||
|
{"CTRL-SHIFT", KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT},
|
||||||
|
{"ALT-SHIFT", KEY_MOD_LEFT_ALT | KEY_MOD_LEFT_SHIFT},
|
||||||
|
{"ALT-GUI", KEY_MOD_LEFT_ALT | KEY_MOD_LEFT_GUI},
|
||||||
|
{"GUI-SHIFT", KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT},
|
||||||
|
|
||||||
|
{"CTRL", KEY_MOD_LEFT_CTRL},
|
||||||
|
{"CONTROL", KEY_MOD_LEFT_CTRL},
|
||||||
|
{"SHIFT", KEY_MOD_LEFT_SHIFT},
|
||||||
|
{"ALT", KEY_MOD_LEFT_ALT},
|
||||||
|
{"GUI", KEY_MOD_LEFT_GUI},
|
||||||
|
{"WINDOWS", KEY_MOD_LEFT_GUI},
|
||||||
|
|
||||||
|
{"DOWNARROW", HID_KEYBOARD_DOWN_ARROW},
|
||||||
|
{"DOWN", HID_KEYBOARD_DOWN_ARROW},
|
||||||
|
{"LEFTARROW", HID_KEYBOARD_LEFT_ARROW},
|
||||||
|
{"LEFT", HID_KEYBOARD_LEFT_ARROW},
|
||||||
|
{"RIGHTARROW", HID_KEYBOARD_RIGHT_ARROW},
|
||||||
|
{"RIGHT", HID_KEYBOARD_RIGHT_ARROW},
|
||||||
|
{"UPARROW", HID_KEYBOARD_UP_ARROW},
|
||||||
|
{"UP", HID_KEYBOARD_UP_ARROW},
|
||||||
|
|
||||||
|
{"ENTER", HID_KEYBOARD_RETURN},
|
||||||
|
{"BREAK", HID_KEYBOARD_PAUSE},
|
||||||
|
{"PAUSE", HID_KEYBOARD_PAUSE},
|
||||||
|
{"CAPSLOCK", HID_KEYBOARD_CAPS_LOCK},
|
||||||
|
{"DELETE", HID_KEYBOARD_DELETE},
|
||||||
|
{"BACKSPACE", HID_KEYPAD_BACKSPACE},
|
||||||
|
{"END", HID_KEYBOARD_END},
|
||||||
|
{"ESC", HID_KEYBOARD_ESCAPE},
|
||||||
|
{"ESCAPE", HID_KEYBOARD_ESCAPE},
|
||||||
|
{"HOME", HID_KEYBOARD_HOME},
|
||||||
|
{"INSERT", HID_KEYBOARD_INSERT},
|
||||||
|
{"NUMLOCK", HID_KEYPAD_NUMLOCK},
|
||||||
|
{"PAGEUP", HID_KEYBOARD_PAGE_UP},
|
||||||
|
{"PAGEDOWN", HID_KEYBOARD_PAGE_DOWN},
|
||||||
|
{"PRINTSCREEN", HID_KEYBOARD_PRINT_SCREEN},
|
||||||
|
{"SCROLLLOCK", HID_KEYBOARD_SCROLL_LOCK},
|
||||||
|
{"SPACE", HID_KEYBOARD_SPACEBAR},
|
||||||
|
{"TAB", HID_KEYBOARD_TAB},
|
||||||
|
{"MENU", HID_KEYBOARD_APPLICATION},
|
||||||
|
{"APP", HID_KEYBOARD_APPLICATION},
|
||||||
|
|
||||||
|
{"F1", HID_KEYBOARD_F1},
|
||||||
|
{"F2", HID_KEYBOARD_F2},
|
||||||
|
{"F3", HID_KEYBOARD_F3},
|
||||||
|
{"F4", HID_KEYBOARD_F4},
|
||||||
|
{"F5", HID_KEYBOARD_F5},
|
||||||
|
{"F6", HID_KEYBOARD_F6},
|
||||||
|
{"F7", HID_KEYBOARD_F7},
|
||||||
|
{"F8", HID_KEYBOARD_F8},
|
||||||
|
{"F9", HID_KEYBOARD_F9},
|
||||||
|
{"F10", HID_KEYBOARD_F10},
|
||||||
|
{"F11", HID_KEYBOARD_F11},
|
||||||
|
{"F12", HID_KEYBOARD_F12},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char ducky_cmd_comment[] = {"REM"};
|
||||||
|
static const char ducky_cmd_id[] = {"ID"};
|
||||||
|
static const char ducky_cmd_delay[] = {"DELAY "};
|
||||||
|
static const char ducky_cmd_string[] = {"STRING "};
|
||||||
|
static const char ducky_cmd_defdelay_1[] = {"DEFAULT_DELAY "};
|
||||||
|
static const char ducky_cmd_defdelay_2[] = {"DEFAULTDELAY "};
|
||||||
|
static const char ducky_cmd_repeat[] = {"REPEAT "};
|
||||||
|
static const char ducky_cmd_sysrq[] = {"SYSRQ "};
|
||||||
|
|
||||||
|
static const char ducky_cmd_altchar[] = {"ALTCHAR "};
|
||||||
|
static const char ducky_cmd_altstr_1[] = {"ALTSTRING "};
|
||||||
|
static const char ducky_cmd_altstr_2[] = {"ALTCODE "};
|
||||||
|
|
||||||
|
static const char ducky_cmd_lang[] = {"DUCKY_LANG"};
|
||||||
|
|
||||||
|
static const uint8_t numpad_keys[10] = {
|
||||||
|
HID_KEYPAD_0,
|
||||||
|
HID_KEYPAD_1,
|
||||||
|
HID_KEYPAD_2,
|
||||||
|
HID_KEYPAD_3,
|
||||||
|
HID_KEYPAD_4,
|
||||||
|
HID_KEYPAD_5,
|
||||||
|
HID_KEYPAD_6,
|
||||||
|
HID_KEYPAD_7,
|
||||||
|
HID_KEYPAD_8,
|
||||||
|
HID_KEYPAD_9,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Wait until there are enough free slots in the keyboard buffer
|
||||||
|
*
|
||||||
|
* @param n_free_chars Number of free slots to wait for (and consider the buffer not full)
|
||||||
|
*/
|
||||||
|
static void bt_hid_hold_while_keyboard_buffer_full(uint8_t n_free_chars, int32_t timeout) {
|
||||||
|
uint32_t start = furi_get_tick();
|
||||||
|
uint32_t timeout_ms = timeout <= -1 ? 0 : timeout;
|
||||||
|
while(furi_hal_bt_hid_kb_free_slots(n_free_chars) == false) {
|
||||||
|
furi_delay_ms(100);
|
||||||
|
|
||||||
|
if(timeout != -1 && (furi_get_tick() - start) > timeout_ms) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ducky_get_number(const char* param, uint32_t* val) {
|
||||||
|
uint32_t value = 0;
|
||||||
|
if(sscanf(param, "%lu", &value) == 1) {
|
||||||
|
*val = value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t ducky_get_command_len(const char* line) {
|
||||||
|
uint32_t len = strlen(line);
|
||||||
|
for(uint32_t i = 0; i < len; i++) {
|
||||||
|
if(line[i] == ' ') return i;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ducky_is_line_end(const char chr) {
|
||||||
|
return ((chr == ' ') || (chr == '\0') || (chr == '\r') || (chr == '\n'));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ducky_numlock_on() {
|
||||||
|
if((furi_hal_hid_get_led_state() & HID_KB_LED_NUM) == 0) {
|
||||||
|
bt_hid_hold_while_keyboard_buffer_full(1, -1);
|
||||||
|
furi_hal_bt_hid_kb_press(HID_KEYBOARD_LOCK_NUM_LOCK);
|
||||||
|
FURI_LOG_I(WORKER_TAG, "BT RSSI: %f\r", (double)furi_hal_bt_get_rssi());
|
||||||
|
furi_delay_ms(25);
|
||||||
|
furi_hal_bt_hid_kb_release(HID_KEYBOARD_LOCK_NUM_LOCK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ducky_numpad_press(const char num) {
|
||||||
|
if((num < '0') || (num > '9')) return false;
|
||||||
|
|
||||||
|
uint16_t key = numpad_keys[num - '0'];
|
||||||
|
bt_hid_hold_while_keyboard_buffer_full(1, -1);
|
||||||
|
FURI_LOG_I(WORKER_TAG, "Pressing %c\r\n", num);
|
||||||
|
|
||||||
|
furi_hal_bt_hid_kb_press(key);
|
||||||
|
FURI_LOG_I(WORKER_TAG, "BT RSSI: %f\r", (double)furi_hal_bt_get_rssi());
|
||||||
|
furi_delay_ms(25);
|
||||||
|
furi_hal_bt_hid_kb_release(key);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ducky_altchar(const char* charcode) {
|
||||||
|
uint8_t i = 0;
|
||||||
|
bool state = false;
|
||||||
|
|
||||||
|
FURI_LOG_I(WORKER_TAG, "char %s", charcode);
|
||||||
|
|
||||||
|
bt_hid_hold_while_keyboard_buffer_full(1, -1);
|
||||||
|
furi_hal_bt_hid_kb_press(KEY_MOD_LEFT_ALT);
|
||||||
|
|
||||||
|
while(!ducky_is_line_end(charcode[i])) {
|
||||||
|
state = ducky_numpad_press(charcode[i]);
|
||||||
|
if(state == false) break;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
furi_hal_bt_hid_kb_release(KEY_MOD_LEFT_ALT);
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ducky_altstring(const char* param) {
|
||||||
|
uint32_t i = 0;
|
||||||
|
bool state = false;
|
||||||
|
|
||||||
|
while(param[i] != '\0') {
|
||||||
|
if((param[i] < ' ') || (param[i] > '~')) {
|
||||||
|
i++;
|
||||||
|
continue; // Skip non-printable chars
|
||||||
|
}
|
||||||
|
|
||||||
|
char temp_str[4];
|
||||||
|
snprintf(temp_str, 4, "%u", param[i]);
|
||||||
|
|
||||||
|
state = ducky_altchar(temp_str);
|
||||||
|
if(state == false) break;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ducky_string(BadBleScript* bad_ble, const char* param) {
|
||||||
|
uint32_t i = 0;
|
||||||
|
while(param[i] != '\0') {
|
||||||
|
uint16_t keycode = BADBLE_ASCII_TO_KEY(bad_ble, param[i]);
|
||||||
|
if(keycode != HID_KEYBOARD_NONE) {
|
||||||
|
bt_hid_hold_while_keyboard_buffer_full(1, -1);
|
||||||
|
furi_hal_bt_hid_kb_press(keycode);
|
||||||
|
FURI_LOG_I(WORKER_TAG, "BT RSSI: %f\r", (double)furi_hal_bt_get_rssi());
|
||||||
|
furi_delay_ms(25);
|
||||||
|
furi_hal_bt_hid_kb_release(keycode);
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint16_t ducky_get_keycode(BadBleScript* bad_ble, const char* param, bool accept_chars) {
|
||||||
|
for(size_t i = 0; i < (sizeof(ducky_keys) / sizeof(ducky_keys[0])); i++) {
|
||||||
|
size_t key_cmd_len = strlen(ducky_keys[i].name);
|
||||||
|
if((strncmp(param, ducky_keys[i].name, key_cmd_len) == 0) &&
|
||||||
|
(ducky_is_line_end(param[key_cmd_len]))) {
|
||||||
|
return ducky_keys[i].keycode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if((accept_chars) && (strlen(param) > 0)) {
|
||||||
|
return (BADBLE_ASCII_TO_KEY(bad_ble, param[0]) & 0xFF);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int32_t
|
||||||
|
ducky_parse_line(BadBleScript* bad_ble, FuriString* line, char* error, size_t error_len) {
|
||||||
|
uint32_t line_len = furi_string_size(line);
|
||||||
|
const char* line_tmp = furi_string_get_cstr(line);
|
||||||
|
bool state = false;
|
||||||
|
|
||||||
|
if(line_len == 0) {
|
||||||
|
return SCRIPT_STATE_NEXT_LINE; // Skip empty lines
|
||||||
|
}
|
||||||
|
|
||||||
|
FURI_LOG_D(WORKER_TAG, "line:%s", line_tmp);
|
||||||
|
|
||||||
|
// General commands
|
||||||
|
if(strncmp(line_tmp, ducky_cmd_comment, strlen(ducky_cmd_comment)) == 0) {
|
||||||
|
// REM - comment line
|
||||||
|
return (0);
|
||||||
|
} else if(strncmp(line_tmp, ducky_cmd_id, strlen(ducky_cmd_id)) == 0) {
|
||||||
|
// ID - executed in ducky_script_preload
|
||||||
|
return (0);
|
||||||
|
} else if(strncmp(line_tmp, ducky_cmd_lang, strlen(ducky_cmd_lang)) == 0) {
|
||||||
|
// DUCKY_LANG - ignore command to retain compatibility with existing scripts
|
||||||
|
return (0);
|
||||||
|
} else if(strncmp(line_tmp, ducky_cmd_delay, strlen(ducky_cmd_delay)) == 0) {
|
||||||
|
// DELAY
|
||||||
|
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
|
||||||
|
uint32_t delay_val = 0;
|
||||||
|
state = ducky_get_number(line_tmp, &delay_val);
|
||||||
|
if((state) && (delay_val > 0)) {
|
||||||
|
return (int32_t)delay_val;
|
||||||
|
}
|
||||||
|
if(error != NULL) {
|
||||||
|
snprintf(error, error_len, "Invalid number %s", line_tmp);
|
||||||
|
}
|
||||||
|
return SCRIPT_STATE_ERROR;
|
||||||
|
} else if(
|
||||||
|
(strncmp(line_tmp, ducky_cmd_defdelay_1, strlen(ducky_cmd_defdelay_1)) == 0) ||
|
||||||
|
(strncmp(line_tmp, ducky_cmd_defdelay_2, strlen(ducky_cmd_defdelay_2)) == 0)) {
|
||||||
|
// DEFAULT_DELAY
|
||||||
|
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
|
||||||
|
state = ducky_get_number(line_tmp, &bad_ble->defdelay);
|
||||||
|
if(!state && error != NULL) {
|
||||||
|
snprintf(error, error_len, "Invalid number %s", line_tmp);
|
||||||
|
}
|
||||||
|
return (state) ? (0) : SCRIPT_STATE_ERROR;
|
||||||
|
} else if(strncmp(line_tmp, ducky_cmd_string, strlen(ducky_cmd_string)) == 0) {
|
||||||
|
// STRING
|
||||||
|
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
|
||||||
|
state = ducky_string(bad_ble, line_tmp);
|
||||||
|
if(!state && error != NULL) {
|
||||||
|
snprintf(error, error_len, "Invalid string %s", line_tmp);
|
||||||
|
}
|
||||||
|
return (state) ? (0) : SCRIPT_STATE_ERROR;
|
||||||
|
} else if(strncmp(line_tmp, ducky_cmd_altchar, strlen(ducky_cmd_altchar)) == 0) {
|
||||||
|
// ALTCHAR
|
||||||
|
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
|
||||||
|
ducky_numlock_on();
|
||||||
|
state = ducky_altchar(line_tmp);
|
||||||
|
if(!state && error != NULL) {
|
||||||
|
snprintf(error, error_len, "Invalid altchar %s", line_tmp);
|
||||||
|
}
|
||||||
|
return (state) ? (0) : SCRIPT_STATE_ERROR;
|
||||||
|
} else if(
|
||||||
|
(strncmp(line_tmp, ducky_cmd_altstr_1, strlen(ducky_cmd_altstr_1)) == 0) ||
|
||||||
|
(strncmp(line_tmp, ducky_cmd_altstr_2, strlen(ducky_cmd_altstr_2)) == 0)) {
|
||||||
|
// ALTSTRING
|
||||||
|
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
|
||||||
|
ducky_numlock_on();
|
||||||
|
state = ducky_altstring(line_tmp);
|
||||||
|
if(!state && error != NULL) {
|
||||||
|
snprintf(error, error_len, "Invalid altstring %s", line_tmp);
|
||||||
|
}
|
||||||
|
return (state) ? (0) : SCRIPT_STATE_ERROR;
|
||||||
|
} else if(strncmp(line_tmp, ducky_cmd_repeat, strlen(ducky_cmd_repeat)) == 0) {
|
||||||
|
// REPEAT
|
||||||
|
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
|
||||||
|
state = ducky_get_number(line_tmp, &bad_ble->repeat_cnt);
|
||||||
|
if(!state && error != NULL) {
|
||||||
|
snprintf(error, error_len, "Invalid number %s", line_tmp);
|
||||||
|
}
|
||||||
|
return (state) ? (0) : SCRIPT_STATE_ERROR;
|
||||||
|
} else if(strncmp(line_tmp, ducky_cmd_sysrq, strlen(ducky_cmd_sysrq)) == 0) {
|
||||||
|
// SYSRQ
|
||||||
|
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
|
||||||
|
uint16_t key = ducky_get_keycode(bad_ble, line_tmp, true);
|
||||||
|
bt_hid_hold_while_keyboard_buffer_full(1, -1);
|
||||||
|
furi_hal_bt_hid_kb_press(KEY_MOD_LEFT_ALT | HID_KEYBOARD_PRINT_SCREEN);
|
||||||
|
furi_hal_bt_hid_kb_press(key);
|
||||||
|
FURI_LOG_I(WORKER_TAG, "BT RSSI: %f\r", (double)furi_hal_bt_get_rssi());
|
||||||
|
furi_delay_ms(25);
|
||||||
|
furi_hal_bt_hid_kb_release(key);
|
||||||
|
furi_hal_bt_hid_kb_release(KEY_MOD_LEFT_ALT | HID_KEYBOARD_PRINT_SCREEN);
|
||||||
|
return (0);
|
||||||
|
} else {
|
||||||
|
// Special keys + modifiers
|
||||||
|
uint16_t key = ducky_get_keycode(bad_ble, line_tmp, false);
|
||||||
|
if(key == HID_KEYBOARD_NONE) {
|
||||||
|
if(error != NULL) {
|
||||||
|
snprintf(error, error_len, "No keycode defined for %s", line_tmp);
|
||||||
|
}
|
||||||
|
return SCRIPT_STATE_ERROR;
|
||||||
|
}
|
||||||
|
if((key & 0xFF00) != 0) {
|
||||||
|
// It's a modifier key
|
||||||
|
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
|
||||||
|
key |= ducky_get_keycode(bad_ble, line_tmp, true);
|
||||||
|
}
|
||||||
|
FURI_LOG_I(WORKER_TAG, "Special key pressed %x\r\n", key);
|
||||||
|
furi_hal_bt_hid_kb_press(key);
|
||||||
|
FURI_LOG_I(WORKER_TAG, "BT RSSI: %f\r", (double)furi_hal_bt_get_rssi());
|
||||||
|
furi_delay_ms(25);
|
||||||
|
furi_hal_bt_hid_kb_release(key);
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ducky_script_preload(BadBleScript* bad_ble, File* script_file) {
|
||||||
|
uint8_t ret = 0;
|
||||||
|
uint32_t line_len = 0;
|
||||||
|
|
||||||
|
furi_string_reset(bad_ble->line);
|
||||||
|
|
||||||
|
do {
|
||||||
|
ret = storage_file_read(script_file, bad_ble->file_buf, FILE_BUFFER_LEN);
|
||||||
|
for(uint16_t i = 0; i < ret; i++) {
|
||||||
|
if(bad_ble->file_buf[i] == '\n' && line_len > 0) {
|
||||||
|
bad_ble->st.line_nb++;
|
||||||
|
line_len = 0;
|
||||||
|
} else {
|
||||||
|
if(bad_ble->st.line_nb == 0) { // Save first line
|
||||||
|
furi_string_push_back(bad_ble->line, bad_ble->file_buf[i]);
|
||||||
|
}
|
||||||
|
line_len++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(storage_file_eof(script_file)) {
|
||||||
|
if(line_len > 0) {
|
||||||
|
bad_ble->st.line_nb++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while(ret > 0);
|
||||||
|
|
||||||
|
storage_file_seek(script_file, 0, true);
|
||||||
|
furi_string_reset(bad_ble->line);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int32_t ducky_script_execute_next(BadBleScript* bad_ble, File* script_file) {
|
||||||
|
int32_t delay_val = 0;
|
||||||
|
|
||||||
|
if(bad_ble->repeat_cnt > 0) {
|
||||||
|
bad_ble->repeat_cnt--;
|
||||||
|
delay_val = ducky_parse_line(
|
||||||
|
bad_ble, bad_ble->line_prev, bad_ble->st.error, sizeof(bad_ble->st.error));
|
||||||
|
if(delay_val == SCRIPT_STATE_NEXT_LINE) { // Empty line
|
||||||
|
return 0;
|
||||||
|
} else if(delay_val < 0) { // Script error
|
||||||
|
bad_ble->st.error_line = bad_ble->st.line_cur - 1;
|
||||||
|
FURI_LOG_E(WORKER_TAG, "Unknown command at line %u", bad_ble->st.line_cur - 1U);
|
||||||
|
return SCRIPT_STATE_ERROR;
|
||||||
|
} else {
|
||||||
|
return (delay_val + bad_ble->defdelay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
furi_string_set(bad_ble->line_prev, bad_ble->line);
|
||||||
|
furi_string_reset(bad_ble->line);
|
||||||
|
|
||||||
|
while(1) {
|
||||||
|
if(bad_ble->buf_len == 0) {
|
||||||
|
bad_ble->buf_len = storage_file_read(script_file, bad_ble->file_buf, FILE_BUFFER_LEN);
|
||||||
|
if(storage_file_eof(script_file)) {
|
||||||
|
if((bad_ble->buf_len < FILE_BUFFER_LEN) && (bad_ble->file_end == false)) {
|
||||||
|
bad_ble->file_buf[bad_ble->buf_len] = '\n';
|
||||||
|
bad_ble->buf_len++;
|
||||||
|
bad_ble->file_end = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bad_ble->buf_start = 0;
|
||||||
|
if(bad_ble->buf_len == 0) return SCRIPT_STATE_END;
|
||||||
|
}
|
||||||
|
for(uint8_t i = bad_ble->buf_start; i < (bad_ble->buf_start + bad_ble->buf_len); i++) {
|
||||||
|
if(bad_ble->file_buf[i] == '\n' && furi_string_size(bad_ble->line) > 0) {
|
||||||
|
bad_ble->st.line_cur++;
|
||||||
|
bad_ble->buf_len = bad_ble->buf_len + bad_ble->buf_start - (i + 1);
|
||||||
|
bad_ble->buf_start = i + 1;
|
||||||
|
furi_string_trim(bad_ble->line);
|
||||||
|
delay_val = ducky_parse_line(
|
||||||
|
bad_ble, bad_ble->line, bad_ble->st.error, sizeof(bad_ble->st.error));
|
||||||
|
if(delay_val == SCRIPT_STATE_NEXT_LINE) { // Empty line
|
||||||
|
return 0;
|
||||||
|
} else if(delay_val < 0) {
|
||||||
|
bad_ble->st.error_line = bad_ble->st.line_cur;
|
||||||
|
if(delay_val == SCRIPT_STATE_NEXT_LINE) {
|
||||||
|
snprintf(
|
||||||
|
bad_ble->st.error, sizeof(bad_ble->st.error), "Forbidden empty line");
|
||||||
|
FURI_LOG_E(
|
||||||
|
WORKER_TAG, "Forbidden empty line at line %u", bad_ble->st.line_cur);
|
||||||
|
} else {
|
||||||
|
FURI_LOG_E(WORKER_TAG, "Unknown command at line %u", bad_ble->st.line_cur);
|
||||||
|
}
|
||||||
|
return SCRIPT_STATE_ERROR;
|
||||||
|
} else {
|
||||||
|
return (delay_val + bad_ble->defdelay);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
furi_string_push_back(bad_ble->line, bad_ble->file_buf[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bad_ble->buf_len = 0;
|
||||||
|
if(bad_ble->file_end) return SCRIPT_STATE_END;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void bad_ble_hid_state_callback(BtStatus status, void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
BadBleScript* bad_ble = (BadBleScript*)context;
|
||||||
|
bool state = (status == BtStatusConnected);
|
||||||
|
|
||||||
|
if(state == true)
|
||||||
|
furi_thread_flags_set(furi_thread_get_id(bad_ble->thread), WorkerEvtConnect);
|
||||||
|
else
|
||||||
|
furi_thread_flags_set(furi_thread_get_id(bad_ble->thread), WorkerEvtDisconnect);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int32_t bad_ble_worker(void* context) {
|
||||||
|
BadBleScript* bad_ble = context;
|
||||||
|
|
||||||
|
BadBleWorkerState worker_state = BadBleStateInit;
|
||||||
|
int32_t delay_val = 0;
|
||||||
|
|
||||||
|
// init ble hid
|
||||||
|
bt_disconnect(bad_ble->bt);
|
||||||
|
|
||||||
|
// Wait 2nd core to update nvm storage
|
||||||
|
furi_delay_ms(200);
|
||||||
|
|
||||||
|
bt_keys_storage_set_storage_path(bad_ble->bt, HID_BT_KEYS_STORAGE_PATH);
|
||||||
|
|
||||||
|
//bt_set_adv_name(bad_ble->bt, "Keyboard K99");
|
||||||
|
|
||||||
|
furi_hal_bt_modify_profile_adv_name("Keyboard K99", BtProfileHidKeyboard);
|
||||||
|
|
||||||
|
if(!bt_set_profile(bad_ble->bt, BtProfileHidKeyboard)) {
|
||||||
|
FURI_LOG_E(TAG, "Failed to switch to HID profile");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
furi_hal_bt_start_advertising();
|
||||||
|
|
||||||
|
FURI_LOG_I(WORKER_TAG, "Init");
|
||||||
|
File* script_file = storage_file_alloc(furi_record_open(RECORD_STORAGE));
|
||||||
|
bad_ble->line = furi_string_alloc();
|
||||||
|
bad_ble->line_prev = furi_string_alloc();
|
||||||
|
|
||||||
|
bt_set_status_changed_callback(bad_ble->bt, bad_ble_hid_state_callback, bad_ble);
|
||||||
|
while(1) {
|
||||||
|
if(worker_state == BadBleStateInit) { // State: initialization
|
||||||
|
if(storage_file_open(
|
||||||
|
script_file,
|
||||||
|
furi_string_get_cstr(bad_ble->file_path),
|
||||||
|
FSAM_READ,
|
||||||
|
FSOM_OPEN_EXISTING)) {
|
||||||
|
if((ducky_script_preload(bad_ble, script_file)) && (bad_ble->st.line_nb > 0)) {
|
||||||
|
worker_state = BadBleStateNotConnected; // Ready to run
|
||||||
|
} else {
|
||||||
|
worker_state = BadBleStateScriptError; // Script preload error
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
FURI_LOG_E(WORKER_TAG, "File open error");
|
||||||
|
worker_state = BadBleStateFileError; // File open error
|
||||||
|
}
|
||||||
|
bad_ble->st.state = worker_state;
|
||||||
|
|
||||||
|
} else if(worker_state == BadBleStateNotConnected) { // State: ble not connected
|
||||||
|
uint32_t flags = furi_thread_flags_wait(
|
||||||
|
WorkerEvtEnd | WorkerEvtConnect | WorkerEvtToggle,
|
||||||
|
FuriFlagWaitAny,
|
||||||
|
FuriWaitForever);
|
||||||
|
furi_check((flags & FuriFlagError) == 0);
|
||||||
|
if(flags & WorkerEvtEnd) {
|
||||||
|
break;
|
||||||
|
} else if(flags & WorkerEvtConnect) {
|
||||||
|
worker_state = BadBleStateIdle; // Ready to run
|
||||||
|
} else if(flags & WorkerEvtToggle) {
|
||||||
|
worker_state = BadBleStateWillRun; // Will run when ble is connected
|
||||||
|
}
|
||||||
|
bad_ble->st.state = worker_state;
|
||||||
|
|
||||||
|
} else if(worker_state == BadBleStateIdle) { // State: ready to start
|
||||||
|
uint32_t flags = furi_thread_flags_wait(
|
||||||
|
WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect,
|
||||||
|
FuriFlagWaitAny,
|
||||||
|
FuriWaitForever);
|
||||||
|
furi_check((flags & FuriFlagError) == 0);
|
||||||
|
if(flags & WorkerEvtEnd) {
|
||||||
|
break;
|
||||||
|
} else if(flags & WorkerEvtToggle) { // Start executing script
|
||||||
|
DOLPHIN_DEED(DolphinDeedBadUsbPlayScript);
|
||||||
|
delay_val = 0;
|
||||||
|
bad_ble->buf_len = 0;
|
||||||
|
bad_ble->st.line_cur = 0;
|
||||||
|
bad_ble->defdelay = 0;
|
||||||
|
bad_ble->repeat_cnt = 0;
|
||||||
|
bad_ble->file_end = false;
|
||||||
|
storage_file_seek(script_file, 0, true);
|
||||||
|
worker_state = BadBleStateRunning;
|
||||||
|
} else if(flags & WorkerEvtDisconnect) {
|
||||||
|
worker_state = BadBleStateNotConnected; // ble disconnected
|
||||||
|
}
|
||||||
|
bad_ble->st.state = worker_state;
|
||||||
|
|
||||||
|
} else if(worker_state == BadBleStateWillRun) { // State: start on connection
|
||||||
|
uint32_t flags = furi_thread_flags_wait(
|
||||||
|
WorkerEvtEnd | WorkerEvtConnect | WorkerEvtToggle,
|
||||||
|
FuriFlagWaitAny,
|
||||||
|
FuriWaitForever);
|
||||||
|
furi_check((flags & FuriFlagError) == 0);
|
||||||
|
if(flags & WorkerEvtEnd) {
|
||||||
|
break;
|
||||||
|
} else if(flags & WorkerEvtConnect) { // Start executing script
|
||||||
|
DOLPHIN_DEED(DolphinDeedBadUsbPlayScript);
|
||||||
|
delay_val = 0;
|
||||||
|
bad_ble->buf_len = 0;
|
||||||
|
bad_ble->st.line_cur = 0;
|
||||||
|
bad_ble->defdelay = 0;
|
||||||
|
bad_ble->repeat_cnt = 0;
|
||||||
|
bad_ble->file_end = false;
|
||||||
|
storage_file_seek(script_file, 0, true);
|
||||||
|
// extra time for PC to recognize Flipper as keyboard
|
||||||
|
furi_thread_flags_wait(0, FuriFlagWaitAny, 1500);
|
||||||
|
worker_state = BadBleStateRunning;
|
||||||
|
} else if(flags & WorkerEvtToggle) { // Cancel scheduled execution
|
||||||
|
worker_state = BadBleStateNotConnected;
|
||||||
|
}
|
||||||
|
bad_ble->st.state = worker_state;
|
||||||
|
|
||||||
|
} else if(worker_state == BadBleStateRunning) { // State: running
|
||||||
|
uint16_t delay_cur = (delay_val > 1000) ? (1000) : (delay_val);
|
||||||
|
uint32_t flags = furi_thread_flags_wait(
|
||||||
|
WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect, FuriFlagWaitAny, delay_cur);
|
||||||
|
delay_val -= delay_cur;
|
||||||
|
if(!(flags & FuriFlagError)) {
|
||||||
|
if(flags & WorkerEvtEnd) {
|
||||||
|
break;
|
||||||
|
} else if(flags & WorkerEvtToggle) {
|
||||||
|
worker_state = BadBleStateIdle; // Stop executing script
|
||||||
|
furi_hal_bt_hid_kb_release_all();
|
||||||
|
} else if(flags & WorkerEvtDisconnect) {
|
||||||
|
worker_state = BadBleStateNotConnected; // ble disconnected
|
||||||
|
furi_hal_bt_hid_kb_release_all();
|
||||||
|
}
|
||||||
|
bad_ble->st.state = worker_state;
|
||||||
|
continue;
|
||||||
|
} else if(
|
||||||
|
(flags == (unsigned)FuriFlagErrorTimeout) ||
|
||||||
|
(flags == (unsigned)FuriFlagErrorResource)) {
|
||||||
|
if(delay_val > 0) {
|
||||||
|
bad_ble->st.delay_remain--;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
bad_ble->st.state = BadBleStateRunning;
|
||||||
|
delay_val = ducky_script_execute_next(bad_ble, script_file);
|
||||||
|
if(delay_val == SCRIPT_STATE_ERROR) { // Script error
|
||||||
|
delay_val = 0;
|
||||||
|
worker_state = BadBleStateScriptError;
|
||||||
|
bad_ble->st.state = worker_state;
|
||||||
|
} else if(delay_val == SCRIPT_STATE_END) { // End of script
|
||||||
|
delay_val = 0;
|
||||||
|
worker_state = BadBleStateIdle;
|
||||||
|
bad_ble->st.state = BadBleStateDone;
|
||||||
|
furi_hal_bt_hid_kb_release_all();
|
||||||
|
continue;
|
||||||
|
} else if(delay_val > 1000) {
|
||||||
|
bad_ble->st.state = BadBleStateDelay; // Show long delays
|
||||||
|
bad_ble->st.delay_remain = delay_val / 1000;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
furi_check((flags & FuriFlagError) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if(
|
||||||
|
(worker_state == BadBleStateFileError) ||
|
||||||
|
(worker_state == BadBleStateScriptError)) { // State: error
|
||||||
|
uint32_t flags = furi_thread_flags_wait(
|
||||||
|
WorkerEvtEnd, FuriFlagWaitAny, FuriWaitForever); // Waiting for exit command
|
||||||
|
furi_check((flags & FuriFlagError) == 0);
|
||||||
|
if(flags & WorkerEvtEnd) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// release all keys
|
||||||
|
bt_hid_hold_while_keyboard_buffer_full(6, 3000);
|
||||||
|
|
||||||
|
// stop ble
|
||||||
|
bt_set_status_changed_callback(bad_ble->bt, NULL, NULL);
|
||||||
|
|
||||||
|
bt_disconnect(bad_ble->bt);
|
||||||
|
|
||||||
|
// Wait 2nd core to update nvm storage
|
||||||
|
furi_delay_ms(200);
|
||||||
|
|
||||||
|
bt_keys_storage_set_default_path(bad_ble->bt);
|
||||||
|
|
||||||
|
if(!bt_set_profile(bad_ble->bt, BtProfileSerial)) {
|
||||||
|
FURI_LOG_E(TAG, "Failed to switch to Serial profile");
|
||||||
|
}
|
||||||
|
|
||||||
|
storage_file_close(script_file);
|
||||||
|
storage_file_free(script_file);
|
||||||
|
furi_string_free(bad_ble->line);
|
||||||
|
furi_string_free(bad_ble->line_prev);
|
||||||
|
|
||||||
|
FURI_LOG_I(WORKER_TAG, "End");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void bad_ble_script_set_default_keyboard_layout(BadBleScript* bad_ble) {
|
||||||
|
furi_assert(bad_ble);
|
||||||
|
memset(bad_ble->layout, HID_KEYBOARD_NONE, sizeof(bad_ble->layout));
|
||||||
|
memcpy(bad_ble->layout, hid_asciimap, MIN(sizeof(hid_asciimap), sizeof(bad_ble->layout)));
|
||||||
|
}
|
||||||
|
|
||||||
|
BadBleScript* bad_ble_script_open(FuriString* file_path, Bt* bt) {
|
||||||
|
furi_assert(file_path);
|
||||||
|
|
||||||
|
BadBleScript* bad_ble = malloc(sizeof(BadBleScript));
|
||||||
|
bad_ble->file_path = furi_string_alloc();
|
||||||
|
furi_string_set(bad_ble->file_path, file_path);
|
||||||
|
bad_ble_script_set_default_keyboard_layout(bad_ble);
|
||||||
|
|
||||||
|
bad_ble->st.state = BadBleStateInit;
|
||||||
|
bad_ble->st.error[0] = '\0';
|
||||||
|
|
||||||
|
bad_ble->bt = bt;
|
||||||
|
|
||||||
|
bad_ble->thread = furi_thread_alloc_ex("BadBleWorker", 2048, bad_ble_worker, bad_ble);
|
||||||
|
furi_thread_start(bad_ble->thread);
|
||||||
|
return bad_ble;
|
||||||
|
} //-V773
|
||||||
|
|
||||||
|
void bad_ble_script_close(BadBleScript* bad_ble) {
|
||||||
|
furi_assert(bad_ble);
|
||||||
|
furi_record_close(RECORD_STORAGE);
|
||||||
|
furi_thread_flags_set(furi_thread_get_id(bad_ble->thread), WorkerEvtEnd);
|
||||||
|
furi_thread_join(bad_ble->thread);
|
||||||
|
furi_thread_free(bad_ble->thread);
|
||||||
|
furi_string_free(bad_ble->file_path);
|
||||||
|
free(bad_ble);
|
||||||
|
}
|
||||||
|
|
||||||
|
void bad_ble_script_set_keyboard_layout(BadBleScript* bad_ble, FuriString* layout_path) {
|
||||||
|
furi_assert(bad_ble);
|
||||||
|
|
||||||
|
if((bad_ble->st.state == BadBleStateRunning) || (bad_ble->st.state == BadBleStateDelay)) {
|
||||||
|
// do not update keyboard layout while a script is running
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
File* layout_file = storage_file_alloc(furi_record_open(RECORD_STORAGE));
|
||||||
|
if(!furi_string_empty(layout_path)) {
|
||||||
|
if(storage_file_open(
|
||||||
|
layout_file, furi_string_get_cstr(layout_path), FSAM_READ, FSOM_OPEN_EXISTING)) {
|
||||||
|
uint16_t layout[128];
|
||||||
|
if(storage_file_read(layout_file, layout, sizeof(layout)) == sizeof(layout)) {
|
||||||
|
memcpy(bad_ble->layout, layout, sizeof(layout));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
storage_file_close(layout_file);
|
||||||
|
} else {
|
||||||
|
bad_ble_script_set_default_keyboard_layout(bad_ble);
|
||||||
|
}
|
||||||
|
storage_file_free(layout_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
void bad_ble_script_toggle(BadBleScript* bad_ble) {
|
||||||
|
furi_assert(bad_ble);
|
||||||
|
furi_thread_flags_set(furi_thread_get_id(bad_ble->thread), WorkerEvtToggle);
|
||||||
|
}
|
||||||
|
|
||||||
|
BadBleState* bad_ble_script_get_state(BadBleScript* bad_ble) {
|
||||||
|
furi_assert(bad_ble);
|
||||||
|
return &(bad_ble->st);
|
||||||
|
}
|
||||||
49
applications/main/bad_ble/bad_ble_script.h
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <furi.h>
|
||||||
|
#include <bt/bt_service/bt_i.h>
|
||||||
|
|
||||||
|
typedef struct BadBleScript BadBleScript;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
BadBleStateInit,
|
||||||
|
BadBleStateNotConnected,
|
||||||
|
BadBleStateIdle,
|
||||||
|
BadBleStateWillRun,
|
||||||
|
BadBleStateRunning,
|
||||||
|
BadBleStateDelay,
|
||||||
|
BadBleStateDone,
|
||||||
|
BadBleStateScriptError,
|
||||||
|
BadBleStateFileError,
|
||||||
|
} BadBleWorkerState;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
BadBleWorkerState state;
|
||||||
|
uint16_t line_cur;
|
||||||
|
uint16_t line_nb;
|
||||||
|
uint32_t delay_remain;
|
||||||
|
uint16_t error_line;
|
||||||
|
char error[64];
|
||||||
|
} BadBleState;
|
||||||
|
|
||||||
|
BadBleScript* bad_ble_script_open(FuriString* file_path, Bt *bt);
|
||||||
|
|
||||||
|
void bad_ble_script_close(BadBleScript* bad_ble);
|
||||||
|
|
||||||
|
void bad_ble_script_set_keyboard_layout(BadBleScript* bad_ble, FuriString* layout_path);
|
||||||
|
|
||||||
|
void bad_ble_script_start(BadBleScript* bad_ble);
|
||||||
|
|
||||||
|
void bad_ble_script_stop(BadBleScript* bad_ble);
|
||||||
|
|
||||||
|
void bad_ble_script_toggle(BadBleScript* bad_ble);
|
||||||
|
|
||||||
|
BadBleState* bad_ble_script_get_state(BadBleScript* bad_ble);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
3
applications/main/bad_ble/bad_ble_settings_filename.h
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define BAD_BLE_SETTINGS_FILE_NAME ".BadBle.settings"
|
||||||
BIN
applications/main/bad_ble/badusb_10px.png
Normal file
|
After Width: | Height: | Size: 576 B |
BIN
applications/main/bad_ble/images/ActiveConnection_50x64.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
applications/main/bad_ble/images/Clock_18x18.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
applications/main/bad_ble/images/Error_18x18.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
applications/main/bad_ble/images/EviSmile1_18x21.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
applications/main/bad_ble/images/EviSmile2_18x21.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
applications/main/bad_ble/images/EviWaiting1_18x21.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
applications/main/bad_ble/images/EviWaiting2_18x21.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
applications/main/bad_ble/images/Percent_10x14.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
applications/main/bad_ble/images/SDQuestion_35x43.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
applications/main/bad_ble/images/Smile_18x18.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
applications/main/bad_ble/images/UsbTree_48x22.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
applications/main/bad_ble/images/badusb_10px.png
Normal file
|
After Width: | Height: | Size: 576 B |
BIN
applications/main/bad_ble/images/keyboard_10px.png
Normal file
|
After Width: | Height: | Size: 147 B |
30
applications/main/bad_ble/scenes/bad_ble_scene.c
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#include "bad_ble_scene.h"
|
||||||
|
|
||||||
|
// Generate scene on_enter handlers array
|
||||||
|
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
|
||||||
|
void (*const bad_ble_scene_on_enter_handlers[])(void*) = {
|
||||||
|
#include "bad_ble_scene_config.h"
|
||||||
|
};
|
||||||
|
#undef ADD_SCENE
|
||||||
|
|
||||||
|
// Generate scene on_event handlers array
|
||||||
|
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
|
||||||
|
bool (*const bad_ble_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = {
|
||||||
|
#include "bad_ble_scene_config.h"
|
||||||
|
};
|
||||||
|
#undef ADD_SCENE
|
||||||
|
|
||||||
|
// Generate scene on_exit handlers array
|
||||||
|
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
|
||||||
|
void (*const bad_ble_scene_on_exit_handlers[])(void* context) = {
|
||||||
|
#include "bad_ble_scene_config.h"
|
||||||
|
};
|
||||||
|
#undef ADD_SCENE
|
||||||
|
|
||||||
|
// Initialize scene handlers configuration structure
|
||||||
|
const SceneManagerHandlers bad_ble_scene_handlers = {
|
||||||
|
.on_enter_handlers = bad_ble_scene_on_enter_handlers,
|
||||||
|
.on_event_handlers = bad_ble_scene_on_event_handlers,
|
||||||
|
.on_exit_handlers = bad_ble_scene_on_exit_handlers,
|
||||||
|
.scene_num = BadBleSceneNum,
|
||||||
|
};
|
||||||
29
applications/main/bad_ble/scenes/bad_ble_scene.h
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) BadBleScene##id,
|
||||||
|
typedef enum {
|
||||||
|
#include "bad_ble_scene_config.h"
|
||||||
|
BadBleSceneNum,
|
||||||
|
} BadBleScene;
|
||||||
|
#undef ADD_SCENE
|
||||||
|
|
||||||
|
extern const SceneManagerHandlers bad_ble_scene_handlers;
|
||||||
|
|
||||||
|
// Generate scene on_enter handlers declaration
|
||||||
|
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
|
||||||
|
#include "bad_ble_scene_config.h"
|
||||||
|
#undef ADD_SCENE
|
||||||
|
|
||||||
|
// Generate scene on_event handlers declaration
|
||||||
|
#define ADD_SCENE(prefix, name, id) \
|
||||||
|
bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event);
|
||||||
|
#include "bad_ble_scene_config.h"
|
||||||
|
#undef ADD_SCENE
|
||||||
|
|
||||||
|
// Generate scene on_exit handlers declaration
|
||||||
|
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context);
|
||||||
|
#include "bad_ble_scene_config.h"
|
||||||
|
#undef ADD_SCENE
|
||||||
52
applications/main/bad_ble/scenes/bad_ble_scene_config.c
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
#include "../bad_ble_app_i.h"
|
||||||
|
#include "furi_hal_power.h"
|
||||||
|
|
||||||
|
enum SubmenuIndex {
|
||||||
|
SubmenuIndexKeyboardLayout,
|
||||||
|
};
|
||||||
|
|
||||||
|
void bad_ble_scene_config_submenu_callback(void* context, uint32_t index) {
|
||||||
|
BadBleApp* bad_ble = context;
|
||||||
|
view_dispatcher_send_custom_event(bad_ble->view_dispatcher, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
void bad_ble_scene_config_on_enter(void* context) {
|
||||||
|
BadBleApp* bad_ble = context;
|
||||||
|
Submenu* submenu = bad_ble->submenu;
|
||||||
|
|
||||||
|
submenu_add_item(
|
||||||
|
submenu,
|
||||||
|
"Keyboard layout",
|
||||||
|
SubmenuIndexKeyboardLayout,
|
||||||
|
bad_ble_scene_config_submenu_callback,
|
||||||
|
bad_ble);
|
||||||
|
|
||||||
|
submenu_set_selected_item(
|
||||||
|
submenu, scene_manager_get_scene_state(bad_ble->scene_manager, BadBleSceneConfig));
|
||||||
|
|
||||||
|
view_dispatcher_switch_to_view(bad_ble->view_dispatcher, BadBleAppViewConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool bad_ble_scene_config_on_event(void* context, SceneManagerEvent event) {
|
||||||
|
BadBleApp* bad_ble = context;
|
||||||
|
bool consumed = false;
|
||||||
|
|
||||||
|
if(event.type == SceneManagerEventTypeCustom) {
|
||||||
|
scene_manager_set_scene_state(bad_ble->scene_manager, BadBleSceneConfig, event.event);
|
||||||
|
consumed = true;
|
||||||
|
if(event.event == SubmenuIndexKeyboardLayout) {
|
||||||
|
scene_manager_next_scene(bad_ble->scene_manager, BadBleSceneConfigLayout);
|
||||||
|
} else {
|
||||||
|
furi_crash("Unknown key type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return consumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void bad_ble_scene_config_on_exit(void* context) {
|
||||||
|
BadBleApp* bad_ble = context;
|
||||||
|
Submenu* submenu = bad_ble->submenu;
|
||||||
|
|
||||||
|
submenu_reset(submenu);
|
||||||
|
}
|
||||||
5
applications/main/bad_ble/scenes/bad_ble_scene_config.h
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
ADD_SCENE(bad_ble, file_select, FileSelect)
|
||||||
|
ADD_SCENE(bad_ble, work, Work)
|
||||||
|
ADD_SCENE(bad_ble, error, Error)
|
||||||
|
ADD_SCENE(bad_ble, config, Config)
|
||||||
|
ADD_SCENE(bad_ble, config_layout, ConfigLayout)
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
#include "../bad_ble_app_i.h"
|
||||||
|
#include "furi_hal_power.h"
|
||||||
|
#include <storage/storage.h>
|
||||||
|
|
||||||
|
static bool bad_ble_layout_select(BadBleApp* bad_ble) {
|
||||||
|
furi_assert(bad_ble);
|
||||||
|
|
||||||
|
FuriString* predefined_path;
|
||||||
|
predefined_path = furi_string_alloc();
|
||||||
|
if(!furi_string_empty(bad_ble->keyboard_layout)) {
|
||||||
|
furi_string_set(predefined_path, bad_ble->keyboard_layout);
|
||||||
|
} else {
|
||||||
|
furi_string_set(predefined_path, BAD_BLE_APP_PATH_LAYOUT_FOLDER);
|
||||||
|
}
|
||||||
|
|
||||||
|
DialogsFileBrowserOptions browser_options;
|
||||||
|
dialog_file_browser_set_basic_options(
|
||||||
|
&browser_options, BAD_BLE_APP_LAYOUT_EXTENSION, &I_keyboard_10px);
|
||||||
|
|
||||||
|
// Input events and views are managed by file_browser
|
||||||
|
bool res = dialog_file_browser_show(
|
||||||
|
bad_ble->dialogs, bad_ble->keyboard_layout, predefined_path, &browser_options);
|
||||||
|
|
||||||
|
furi_string_free(predefined_path);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
void bad_ble_scene_config_layout_on_enter(void* context) {
|
||||||
|
BadBleApp* bad_ble = context;
|
||||||
|
|
||||||
|
if(bad_ble_layout_select(bad_ble)) {
|
||||||
|
bad_ble_script_set_keyboard_layout(bad_ble->bad_ble_script, bad_ble->keyboard_layout);
|
||||||
|
}
|
||||||
|
scene_manager_previous_scene(bad_ble->scene_manager);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool bad_ble_scene_config_layout_on_event(void* context, SceneManagerEvent event) {
|
||||||
|
UNUSED(context);
|
||||||
|
UNUSED(event);
|
||||||
|
// BadBleApp* bad_ble = context;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void bad_ble_scene_config_layout_on_exit(void* context) {
|
||||||
|
UNUSED(context);
|
||||||
|
// BadBleApp* bad_ble = context;
|
||||||
|
}
|
||||||
83
applications/main/bad_ble/scenes/bad_ble_scene_error.c
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
#include "../bad_ble_app_i.h"
|
||||||
|
#include "../../../settings/desktop_settings/desktop_settings_app.h"
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
BadBleCustomEventErrorBack,
|
||||||
|
} BadBleCustomEvent;
|
||||||
|
|
||||||
|
static void
|
||||||
|
bad_ble_scene_error_event_callback(GuiButtonType result, InputType type, void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
BadBleApp* app = context;
|
||||||
|
|
||||||
|
if((result == GuiButtonTypeLeft) && (type == InputTypeShort)) {
|
||||||
|
view_dispatcher_send_custom_event(app->view_dispatcher, BadBleCustomEventErrorBack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void bad_ble_scene_error_on_enter(void* context) {
|
||||||
|
BadBleApp* app = context;
|
||||||
|
DesktopSettings* settings = malloc(sizeof(DesktopSettings));
|
||||||
|
DESKTOP_SETTINGS_LOAD(settings);
|
||||||
|
|
||||||
|
if(app->error == BadBleAppErrorNoFiles) {
|
||||||
|
widget_add_icon_element(app->widget, 0, 0, &I_SDQuestion_35x43);
|
||||||
|
widget_add_string_multiline_element(
|
||||||
|
app->widget,
|
||||||
|
81,
|
||||||
|
4,
|
||||||
|
AlignCenter,
|
||||||
|
AlignTop,
|
||||||
|
FontSecondary,
|
||||||
|
"No SD card or\napp data found.\nThis app will not\nwork without\nrequired files.");
|
||||||
|
widget_add_button_element(
|
||||||
|
app->widget, GuiButtonTypeLeft, "Back", bad_ble_scene_error_event_callback, app);
|
||||||
|
} else if(app->error == BadBleAppErrorCloseRpc) {
|
||||||
|
widget_add_icon_element(app->widget, 78, 0, &I_ActiveConnection_50x64);
|
||||||
|
if (settings->sfw_mode) {
|
||||||
|
widget_add_string_multiline_element(
|
||||||
|
app->widget, 3, 2, AlignLeft, AlignTop, FontPrimary, "Connection\nis active!");
|
||||||
|
widget_add_string_multiline_element(
|
||||||
|
app->widget,
|
||||||
|
3,
|
||||||
|
30,
|
||||||
|
AlignLeft,
|
||||||
|
AlignTop,
|
||||||
|
FontSecondary,
|
||||||
|
"Disconnect from\nPC or phone to\nuse this function.");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
widget_add_string_multiline_element(
|
||||||
|
app->widget, 3, 2, AlignLeft, AlignTop, FontPrimary, "I am not\na whore!");
|
||||||
|
widget_add_string_multiline_element(
|
||||||
|
app->widget,
|
||||||
|
3,
|
||||||
|
30,
|
||||||
|
AlignLeft,
|
||||||
|
AlignTop,
|
||||||
|
FontSecondary,
|
||||||
|
"Pull out from\nPC or phone to\nuse me like this.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
view_dispatcher_switch_to_view(app->view_dispatcher, BadBleAppViewError);
|
||||||
|
free(settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool bad_ble_scene_error_on_event(void* context, SceneManagerEvent event) {
|
||||||
|
BadBleApp* app = context;
|
||||||
|
bool consumed = false;
|
||||||
|
|
||||||
|
if(event.type == SceneManagerEventTypeCustom) {
|
||||||
|
if(event.event == BadBleCustomEventErrorBack) {
|
||||||
|
view_dispatcher_stop(app->view_dispatcher);
|
||||||
|
consumed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return consumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void bad_ble_scene_error_on_exit(void* context) {
|
||||||
|
BadBleApp* app = context;
|
||||||
|
widget_reset(app->widget);
|
||||||
|
}
|
||||||
51
applications/main/bad_ble/scenes/bad_ble_scene_file_select.c
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
#include "../bad_ble_app_i.h"
|
||||||
|
#include "furi_hal_power.h"
|
||||||
|
#include <storage/storage.h>
|
||||||
|
|
||||||
|
static bool bad_ble_file_select(BadBleApp* bad_ble) {
|
||||||
|
furi_assert(bad_ble);
|
||||||
|
|
||||||
|
DialogsFileBrowserOptions browser_options;
|
||||||
|
dialog_file_browser_set_basic_options(
|
||||||
|
&browser_options, BAD_BLE_APP_SCRIPT_EXTENSION, &I_badusb_10px);
|
||||||
|
browser_options.base_path = BAD_BLE_APP_BASE_FOLDER;
|
||||||
|
browser_options.skip_assets = true;
|
||||||
|
|
||||||
|
// Input events and views are managed by file_browser
|
||||||
|
bool res = dialog_file_browser_show(
|
||||||
|
bad_ble->dialogs, bad_ble->file_path, bad_ble->file_path, &browser_options);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
void bad_ble_scene_file_select_on_enter(void* context) {
|
||||||
|
BadBleApp* bad_ble = context;
|
||||||
|
|
||||||
|
// furi_hal_ble_disable();
|
||||||
|
if(bad_ble->bad_ble_script) {
|
||||||
|
bad_ble_script_close(bad_ble->bad_ble_script);
|
||||||
|
bad_ble->bad_ble_script = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(bad_ble_file_select(bad_ble)) {
|
||||||
|
bad_ble->bad_ble_script = bad_ble_script_open(bad_ble->file_path, bad_ble->bt);
|
||||||
|
bad_ble_script_set_keyboard_layout(bad_ble->bad_ble_script, bad_ble->keyboard_layout);
|
||||||
|
|
||||||
|
scene_manager_next_scene(bad_ble->scene_manager, BadBleSceneWork);
|
||||||
|
} else {
|
||||||
|
// furi_hal_ble_enable();
|
||||||
|
view_dispatcher_stop(bad_ble->view_dispatcher);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool bad_ble_scene_file_select_on_event(void* context, SceneManagerEvent event) {
|
||||||
|
UNUSED(context);
|
||||||
|
UNUSED(event);
|
||||||
|
// BadBleApp* bad_ble = context;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void bad_ble_scene_file_select_on_exit(void* context) {
|
||||||
|
UNUSED(context);
|
||||||
|
// BadBleApp* bad_ble = context;
|
||||||
|
}
|
||||||
54
applications/main/bad_ble/scenes/bad_ble_scene_work.c
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
#include "../bad_ble_script.h"
|
||||||
|
#include "../bad_ble_app_i.h"
|
||||||
|
#include "../views/bad_ble_view.h"
|
||||||
|
#include "furi_hal.h"
|
||||||
|
#include "toolbox/path.h"
|
||||||
|
|
||||||
|
void bad_ble_scene_work_button_callback(InputKey key, void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
BadBleApp* app = context;
|
||||||
|
view_dispatcher_send_custom_event(app->view_dispatcher, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool bad_ble_scene_work_on_event(void* context, SceneManagerEvent event) {
|
||||||
|
BadBleApp* app = context;
|
||||||
|
bool consumed = false;
|
||||||
|
|
||||||
|
if(event.type == SceneManagerEventTypeCustom) {
|
||||||
|
if(event.event == InputKeyLeft) {
|
||||||
|
scene_manager_next_scene(app->scene_manager, BadBleSceneConfig);
|
||||||
|
consumed = true;
|
||||||
|
} else if(event.event == InputKeyOk) {
|
||||||
|
bad_ble_script_toggle(app->bad_ble_script);
|
||||||
|
consumed = true;
|
||||||
|
}
|
||||||
|
} else if(event.type == SceneManagerEventTypeTick) {
|
||||||
|
bad_ble_set_state(app->bad_ble_view, bad_ble_script_get_state(app->bad_ble_script));
|
||||||
|
}
|
||||||
|
return consumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void bad_ble_scene_work_on_enter(void* context) {
|
||||||
|
BadBleApp* app = context;
|
||||||
|
|
||||||
|
FuriString* file_name;
|
||||||
|
file_name = furi_string_alloc();
|
||||||
|
path_extract_filename(app->file_path, file_name, true);
|
||||||
|
bad_ble_set_file_name(app->bad_ble_view, furi_string_get_cstr(file_name));
|
||||||
|
furi_string_free(file_name);
|
||||||
|
|
||||||
|
FuriString* layout;
|
||||||
|
layout = furi_string_alloc();
|
||||||
|
path_extract_filename(app->keyboard_layout, layout, true);
|
||||||
|
bad_ble_set_layout(app->bad_ble_view, furi_string_get_cstr(layout));
|
||||||
|
furi_string_free(layout);
|
||||||
|
|
||||||
|
bad_ble_set_state(app->bad_ble_view, bad_ble_script_get_state(app->bad_ble_script));
|
||||||
|
|
||||||
|
bad_ble_set_button_callback(app->bad_ble_view, bad_ble_scene_work_button_callback, app);
|
||||||
|
view_dispatcher_switch_to_view(app->view_dispatcher, BadBleAppViewWork);
|
||||||
|
}
|
||||||
|
|
||||||
|
void bad_ble_scene_work_on_exit(void* context) {
|
||||||
|
UNUSED(context);
|
||||||
|
}
|
||||||
24
applications/main/bad_ble/switch_ble.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
def analyze_and_replace(directory):
|
||||||
|
# Recursively search for .c and .h files in the given directory
|
||||||
|
for root, dirs, files in os.walk(directory):
|
||||||
|
for file in files:
|
||||||
|
if file.endswith(".c") or file.endswith(".h"):
|
||||||
|
# Read the contents of the file
|
||||||
|
with open(os.path.join(root, file), "r") as f:
|
||||||
|
contents = f.read()
|
||||||
|
|
||||||
|
# Replace all occurrences of "BadUsb" and "bad_usb" with "BadBle" and "bad_ble"
|
||||||
|
contents = contents.replace("usb", "ble")
|
||||||
|
contents = contents.replace("USB", "BLE")
|
||||||
|
contents = contents.replace("Usb", "Ble")
|
||||||
|
|
||||||
|
|
||||||
|
# Write the modified contents back to the file
|
||||||
|
with open(os.path.join(root, file), "w") as f:
|
||||||
|
f.write(contents)
|
||||||
|
|
||||||
|
# Test the function with a sample directory
|
||||||
|
analyze_and_replace(".")
|
||||||
237
applications/main/bad_ble/views/bad_ble_view.c
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
#include "bad_ble_view.h"
|
||||||
|
#include "../bad_ble_script.h"
|
||||||
|
#include <toolbox/path.h>
|
||||||
|
#include <gui/elements.h>
|
||||||
|
#include <bad_ble_icons.h>
|
||||||
|
#include "../../../settings/desktop_settings/desktop_settings_app.h"
|
||||||
|
|
||||||
|
#define MAX_NAME_LEN 64
|
||||||
|
|
||||||
|
struct BadBle {
|
||||||
|
View* view;
|
||||||
|
BadBleButtonCallback callback;
|
||||||
|
void* context;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char file_name[MAX_NAME_LEN];
|
||||||
|
char layout[MAX_NAME_LEN];
|
||||||
|
BadBleState state;
|
||||||
|
uint8_t anim_frame;
|
||||||
|
} BadBleModel;
|
||||||
|
|
||||||
|
static void bad_ble_draw_callback(Canvas* canvas, void* _model) {
|
||||||
|
BadBleModel* model = _model;
|
||||||
|
|
||||||
|
FuriString* disp_str;
|
||||||
|
disp_str = furi_string_alloc_set(model->file_name);
|
||||||
|
elements_string_fit_width(canvas, disp_str, 128 - 2);
|
||||||
|
canvas_set_font(canvas, FontSecondary);
|
||||||
|
canvas_draw_str(canvas, 2, 8, furi_string_get_cstr(disp_str));
|
||||||
|
DesktopSettings* settings = malloc(sizeof(DesktopSettings));
|
||||||
|
DESKTOP_SETTINGS_LOAD(settings);
|
||||||
|
|
||||||
|
if(strlen(model->layout) == 0) {
|
||||||
|
furi_string_set(disp_str, "(default)");
|
||||||
|
} else {
|
||||||
|
furi_string_reset(disp_str);
|
||||||
|
furi_string_push_back(disp_str, '(');
|
||||||
|
for(size_t i = 0; i < strlen(model->layout); i++)
|
||||||
|
furi_string_push_back(disp_str, model->layout[i]);
|
||||||
|
furi_string_push_back(disp_str, ')');
|
||||||
|
}
|
||||||
|
elements_string_fit_width(canvas, disp_str, 128 - 2);
|
||||||
|
canvas_draw_str(
|
||||||
|
canvas, 2, 8 + canvas_current_font_height(canvas), furi_string_get_cstr(disp_str));
|
||||||
|
|
||||||
|
furi_string_reset(disp_str);
|
||||||
|
|
||||||
|
canvas_draw_icon(canvas, 22, 24, &I_UsbTree_48x22);
|
||||||
|
|
||||||
|
if((model->state.state == BadBleStateIdle) || (model->state.state == BadBleStateDone) ||
|
||||||
|
(model->state.state == BadBleStateNotConnected)) {
|
||||||
|
if (settings->sfw_mode) {
|
||||||
|
elements_button_center(canvas, "Start");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
elements_button_center(canvas, "Cum");
|
||||||
|
}
|
||||||
|
} else if((model->state.state == BadBleStateRunning) || (model->state.state == BadBleStateDelay)) {
|
||||||
|
elements_button_center(canvas, "Stop");
|
||||||
|
} else if(model->state.state == BadBleStateWillRun) {
|
||||||
|
elements_button_center(canvas, "Cancel");
|
||||||
|
}
|
||||||
|
|
||||||
|
if((model->state.state == BadBleStateNotConnected) ||
|
||||||
|
(model->state.state == BadBleStateIdle) || (model->state.state == BadBleStateDone)) {
|
||||||
|
elements_button_left(canvas, "Config");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(model->state.state == BadBleStateNotConnected) {
|
||||||
|
canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18);
|
||||||
|
canvas_set_font(canvas, FontPrimary);
|
||||||
|
if (settings->sfw_mode) {
|
||||||
|
canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Connect me");
|
||||||
|
canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "to a computer");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Plug me");
|
||||||
|
canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "in, Daddy");
|
||||||
|
}
|
||||||
|
} else if(model->state.state == BadBleStateWillRun) {
|
||||||
|
canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18);
|
||||||
|
canvas_set_font(canvas, FontPrimary);
|
||||||
|
if (settings->sfw_mode) {
|
||||||
|
canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Will run");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Will cum");
|
||||||
|
}
|
||||||
|
canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "on connect");
|
||||||
|
} else if(model->state.state == BadBleStateFileError) {
|
||||||
|
canvas_draw_icon(canvas, 4, 26, &I_Error_18x18);
|
||||||
|
canvas_set_font(canvas, FontPrimary);
|
||||||
|
canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "File");
|
||||||
|
canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "ERROR");
|
||||||
|
} else if(model->state.state == BadBleStateScriptError) {
|
||||||
|
canvas_draw_icon(canvas, 4, 26, &I_Error_18x18);
|
||||||
|
canvas_set_font(canvas, FontPrimary);
|
||||||
|
canvas_draw_str_aligned(canvas, 127, 33, AlignRight, AlignBottom, "ERROR:");
|
||||||
|
canvas_set_font(canvas, FontSecondary);
|
||||||
|
furi_string_printf(disp_str, "line %u", model->state.error_line);
|
||||||
|
canvas_draw_str_aligned(
|
||||||
|
canvas, 127, 46, AlignRight, AlignBottom, furi_string_get_cstr(disp_str));
|
||||||
|
furi_string_reset(disp_str);
|
||||||
|
canvas_draw_str_aligned(canvas, 127, 56, AlignRight, AlignBottom, model->state.error);
|
||||||
|
} else if(model->state.state == BadBleStateIdle) {
|
||||||
|
canvas_draw_icon(canvas, 4, 26, &I_Smile_18x18);
|
||||||
|
canvas_set_font(canvas, FontBigNumbers);
|
||||||
|
canvas_draw_str_aligned(canvas, 114, 40, AlignRight, AlignBottom, "0");
|
||||||
|
canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14);
|
||||||
|
} else if(model->state.state == BadBleStateRunning) {
|
||||||
|
if(model->anim_frame == 0) {
|
||||||
|
canvas_draw_icon(canvas, 4, 23, &I_EviSmile1_18x21);
|
||||||
|
} else {
|
||||||
|
canvas_draw_icon(canvas, 4, 23, &I_EviSmile2_18x21);
|
||||||
|
}
|
||||||
|
canvas_set_font(canvas, FontBigNumbers);
|
||||||
|
furi_string_printf(
|
||||||
|
disp_str, "%u", ((model->state.line_cur - 1) * 100) / model->state.line_nb);
|
||||||
|
canvas_draw_str_aligned(
|
||||||
|
canvas, 114, 40, AlignRight, AlignBottom, furi_string_get_cstr(disp_str));
|
||||||
|
furi_string_reset(disp_str);
|
||||||
|
canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14);
|
||||||
|
} else if(model->state.state == BadBleStateDone) {
|
||||||
|
canvas_draw_icon(canvas, 4, 23, &I_EviSmile1_18x21);
|
||||||
|
canvas_set_font(canvas, FontBigNumbers);
|
||||||
|
canvas_draw_str_aligned(canvas, 114, 40, AlignRight, AlignBottom, "100");
|
||||||
|
furi_string_reset(disp_str);
|
||||||
|
canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14);
|
||||||
|
} else if(model->state.state == BadBleStateDelay) {
|
||||||
|
if(model->anim_frame == 0) {
|
||||||
|
canvas_draw_icon(canvas, 4, 23, &I_EviWaiting1_18x21);
|
||||||
|
} else {
|
||||||
|
canvas_draw_icon(canvas, 4, 23, &I_EviWaiting2_18x21);
|
||||||
|
}
|
||||||
|
canvas_set_font(canvas, FontBigNumbers);
|
||||||
|
furi_string_printf(
|
||||||
|
disp_str, "%u", ((model->state.line_cur - 1) * 100) / model->state.line_nb);
|
||||||
|
canvas_draw_str_aligned(
|
||||||
|
canvas, 114, 40, AlignRight, AlignBottom, furi_string_get_cstr(disp_str));
|
||||||
|
furi_string_reset(disp_str);
|
||||||
|
canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14);
|
||||||
|
canvas_set_font(canvas, FontSecondary);
|
||||||
|
furi_string_printf(disp_str, "delay %lus", model->state.delay_remain);
|
||||||
|
canvas_draw_str_aligned(
|
||||||
|
canvas, 127, 50, AlignRight, AlignBottom, furi_string_get_cstr(disp_str));
|
||||||
|
furi_string_reset(disp_str);
|
||||||
|
} else {
|
||||||
|
canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18);
|
||||||
|
}
|
||||||
|
|
||||||
|
furi_string_free(disp_str);
|
||||||
|
free(settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool bad_ble_input_callback(InputEvent* event, void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
BadBle* bad_ble = context;
|
||||||
|
bool consumed = false;
|
||||||
|
|
||||||
|
if(event->type == InputTypeShort) {
|
||||||
|
if((event->key == InputKeyLeft) || (event->key == InputKeyOk)) {
|
||||||
|
consumed = true;
|
||||||
|
furi_assert(bad_ble->callback);
|
||||||
|
bad_ble->callback(event->key, bad_ble->context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return consumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
BadBle* bad_ble_alloc() {
|
||||||
|
BadBle* bad_ble = malloc(sizeof(BadBle));
|
||||||
|
|
||||||
|
bad_ble->view = view_alloc();
|
||||||
|
view_allocate_model(bad_ble->view, ViewModelTypeLocking, sizeof(BadBleModel));
|
||||||
|
view_set_context(bad_ble->view, bad_ble);
|
||||||
|
view_set_draw_callback(bad_ble->view, bad_ble_draw_callback);
|
||||||
|
view_set_input_callback(bad_ble->view, bad_ble_input_callback);
|
||||||
|
|
||||||
|
return bad_ble;
|
||||||
|
}
|
||||||
|
|
||||||
|
void bad_ble_free(BadBle* bad_ble) {
|
||||||
|
furi_assert(bad_ble);
|
||||||
|
view_free(bad_ble->view);
|
||||||
|
free(bad_ble);
|
||||||
|
}
|
||||||
|
|
||||||
|
View* bad_ble_get_view(BadBle* bad_ble) {
|
||||||
|
furi_assert(bad_ble);
|
||||||
|
return bad_ble->view;
|
||||||
|
}
|
||||||
|
|
||||||
|
void bad_ble_set_button_callback(BadBle* bad_ble, BadBleButtonCallback callback, void* context) {
|
||||||
|
furi_assert(bad_ble);
|
||||||
|
furi_assert(callback);
|
||||||
|
with_view_model(
|
||||||
|
bad_ble->view,
|
||||||
|
BadBleModel * model,
|
||||||
|
{
|
||||||
|
UNUSED(model);
|
||||||
|
bad_ble->callback = callback;
|
||||||
|
bad_ble->context = context;
|
||||||
|
},
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void bad_ble_set_file_name(BadBle* bad_ble, const char* name) {
|
||||||
|
furi_assert(name);
|
||||||
|
with_view_model(
|
||||||
|
bad_ble->view,
|
||||||
|
BadBleModel * model,
|
||||||
|
{ strlcpy(model->file_name, name, MAX_NAME_LEN); },
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void bad_ble_set_layout(BadBle* bad_ble, const char* layout) {
|
||||||
|
furi_assert(layout);
|
||||||
|
with_view_model(
|
||||||
|
bad_ble->view,
|
||||||
|
BadBleModel * model,
|
||||||
|
{ strlcpy(model->layout, layout, MAX_NAME_LEN); },
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void bad_ble_set_state(BadBle* bad_ble, BadBleState* st) {
|
||||||
|
furi_assert(st);
|
||||||
|
with_view_model(
|
||||||
|
bad_ble->view,
|
||||||
|
BadBleModel * model,
|
||||||
|
{
|
||||||
|
memcpy(&(model->state), st, sizeof(BadBleState));
|
||||||
|
model->anim_frame ^= 1;
|
||||||
|
},
|
||||||
|
true);
|
||||||
|
}
|
||||||
21
applications/main/bad_ble/views/bad_ble_view.h
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <gui/view.h>
|
||||||
|
#include "../bad_ble_script.h"
|
||||||
|
|
||||||
|
typedef struct BadBle BadBle;
|
||||||
|
typedef void (*BadBleButtonCallback)(InputKey key, void* context);
|
||||||
|
|
||||||
|
BadBle* bad_ble_alloc();
|
||||||
|
|
||||||
|
void bad_ble_free(BadBle* bad_ble);
|
||||||
|
|
||||||
|
View* bad_ble_get_view(BadBle* bad_ble);
|
||||||
|
|
||||||
|
void bad_ble_set_button_callback(BadBle* bad_ble, BadBleButtonCallback callback, void* context);
|
||||||
|
|
||||||
|
void bad_ble_set_file_name(BadBle* bad_ble, const char* name);
|
||||||
|
|
||||||
|
void bad_ble_set_layout(BadBle* bad_ble, const char* layout);
|
||||||
|
|
||||||
|
void bad_ble_set_state(BadBle* bad_ble, BadBleState* st);
|
||||||