mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-05-11 06:09:08 -07:00
Merge branch 'reborned/some_api_adjustments' of github.com:RebornedBrain/flipperzero-firmware into reborned/some_api_adjustments
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
#include <furi.h>
|
||||
#include <gui/gui.h>
|
||||
#include <gui/canvas_i.h>
|
||||
#include <input/input.h>
|
||||
|
||||
#define BUFFER_SIZE (32U)
|
||||
@@ -42,10 +41,11 @@ static DirectDraw* direct_draw_alloc(void) {
|
||||
static void direct_draw_free(DirectDraw* instance) {
|
||||
furi_pubsub_unsubscribe(instance->input, instance->input_subscription);
|
||||
|
||||
instance->canvas = NULL;
|
||||
gui_direct_draw_release(instance->gui);
|
||||
furi_record_close(RECORD_GUI);
|
||||
furi_record_close(RECORD_INPUT_EVENTS);
|
||||
|
||||
free(instance);
|
||||
}
|
||||
|
||||
static void direct_draw_block(Canvas* canvas, uint32_t size, uint32_t counter) {
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
# Number Input
|
||||
# Number Input {#example_number_input}
|
||||
|
||||
Simple keyboard that limits user inputs to a full number (integer). Useful to enforce correct entries without the need of intense validations after a user input.
|
||||
Simple keyboard that limits user inputs to a full number (integer). Useful to enforce correct entries without the need for intense validations after a user input.
|
||||
|
||||
Definition of min/max values is required. Numbers are of type int32_t. If negative numbers are allowed withing min - max, an additional button is displayed to switch the sign between + and -.
|
||||
## Source code
|
||||
|
||||
It is also possible to define a header text, shown in this example app with the 3 different input options.
|
||||
Source code for this example can be found [here](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/examples/example_number_input).
|
||||
|
||||
## General principle
|
||||
|
||||
Definition of min/max values is required. Numbers are of type int32_t. If negative numbers are allowed within min - max, an additional button is displayed to switch the sign between + and -.
|
||||
|
||||
It is also possible to define a header text, as shown in this example app with the 3 different input options.
|
||||
@@ -7,7 +7,7 @@ App(
|
||||
icon="A_BadUsb_14",
|
||||
order=70,
|
||||
resources="resources",
|
||||
fap_libs=["assets", "ble_profile"],
|
||||
fap_libs=["assets"],
|
||||
fap_icon="icon.png",
|
||||
fap_category="USB",
|
||||
)
|
||||
|
||||
@@ -35,7 +35,6 @@ static void bad_usb_load_settings(BadUsbApp* app) {
|
||||
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
uint32_t version = 0;
|
||||
uint32_t interface = 0;
|
||||
|
||||
if(flipper_format_file_open_existing(fff, BAD_USB_SETTINGS_PATH)) {
|
||||
do {
|
||||
@@ -45,8 +44,6 @@ static void bad_usb_load_settings(BadUsbApp* app) {
|
||||
break;
|
||||
|
||||
if(!flipper_format_read_string(fff, "layout", temp_str)) break;
|
||||
if(!flipper_format_read_uint32(fff, "interface", &interface, 1)) break;
|
||||
if(interface > BadUsbHidInterfaceBle) break;
|
||||
|
||||
state = true;
|
||||
} while(0);
|
||||
@@ -56,7 +53,6 @@ static void bad_usb_load_settings(BadUsbApp* app) {
|
||||
|
||||
if(state) {
|
||||
furi_string_set(app->keyboard_layout, temp_str);
|
||||
app->interface = interface;
|
||||
|
||||
Storage* fs_api = furi_record_open(RECORD_STORAGE);
|
||||
FileInfo layout_file_info;
|
||||
@@ -68,7 +64,6 @@ static void bad_usb_load_settings(BadUsbApp* app) {
|
||||
}
|
||||
} else {
|
||||
furi_string_set(app->keyboard_layout, BAD_USB_SETTINGS_DEFAULT_LAYOUT);
|
||||
app->interface = BadUsbHidInterfaceUsb;
|
||||
}
|
||||
|
||||
furi_string_free(temp_str);
|
||||
@@ -84,9 +79,6 @@ static void bad_usb_save_settings(BadUsbApp* app) {
|
||||
fff, BAD_USB_SETTINGS_FILE_TYPE, BAD_USB_SETTINGS_VERSION))
|
||||
break;
|
||||
if(!flipper_format_write_string(fff, "layout", app->keyboard_layout)) break;
|
||||
uint32_t interface_id = app->interface;
|
||||
if(!flipper_format_write_uint32(fff, "interface", (const uint32_t*)&interface_id, 1))
|
||||
break;
|
||||
} while(0);
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,6 @@ struct BadUsbApp {
|
||||
BadUsb* bad_usb_view;
|
||||
BadUsbScript* bad_usb_script;
|
||||
|
||||
BadUsbHidInterface interface;
|
||||
FuriHalUsbInterface* usb_if_prev;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
#include "bad_usb_hid.h"
|
||||
#include <extra_profiles/hid_profile.h>
|
||||
#include <bt/bt_service/bt.h>
|
||||
#include <storage/storage.h>
|
||||
|
||||
#define TAG "BadUSB HID"
|
||||
|
||||
#define HID_BT_KEYS_STORAGE_NAME ".bt_hid.keys"
|
||||
|
||||
void* hid_usb_init(FuriHalUsbHidConfig* hid_cfg) {
|
||||
furi_check(furi_hal_usb_set_config(&usb_hid, hid_cfg));
|
||||
return NULL;
|
||||
@@ -72,155 +69,6 @@ static const BadUsbHidApi hid_api_usb = {
|
||||
.release_all = hid_usb_release_all,
|
||||
.get_led_state = hid_usb_get_led_state,
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
Bt* bt;
|
||||
FuriHalBleProfileBase* profile;
|
||||
HidStateCallback state_callback;
|
||||
void* callback_context;
|
||||
bool is_connected;
|
||||
} BleHidInstance;
|
||||
|
||||
static const BleProfileHidParams ble_hid_params = {
|
||||
.device_name_prefix = "BadUSB",
|
||||
.mac_xor = 0x0002,
|
||||
};
|
||||
|
||||
static void hid_ble_connection_status_callback(BtStatus status, void* context) {
|
||||
furi_assert(context);
|
||||
BleHidInstance* ble_hid = context;
|
||||
ble_hid->is_connected = (status == BtStatusConnected);
|
||||
if(ble_hid->state_callback) {
|
||||
ble_hid->state_callback(ble_hid->is_connected, ble_hid->callback_context);
|
||||
}
|
||||
}
|
||||
|
||||
void* hid_ble_init(FuriHalUsbHidConfig* hid_cfg) {
|
||||
UNUSED(hid_cfg);
|
||||
BleHidInstance* ble_hid = malloc(sizeof(BleHidInstance));
|
||||
ble_hid->bt = furi_record_open(RECORD_BT);
|
||||
bt_disconnect(ble_hid->bt);
|
||||
|
||||
// Wait 2nd core to update nvm storage
|
||||
furi_delay_ms(200);
|
||||
|
||||
bt_keys_storage_set_storage_path(ble_hid->bt, APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME));
|
||||
|
||||
ble_hid->profile = bt_profile_start(ble_hid->bt, ble_profile_hid, (void*)&ble_hid_params);
|
||||
furi_check(ble_hid->profile);
|
||||
|
||||
furi_hal_bt_start_advertising();
|
||||
|
||||
bt_set_status_changed_callback(ble_hid->bt, hid_ble_connection_status_callback, ble_hid);
|
||||
|
||||
return ble_hid;
|
||||
}
|
||||
|
||||
void hid_ble_deinit(void* inst) {
|
||||
BleHidInstance* ble_hid = inst;
|
||||
furi_assert(ble_hid);
|
||||
|
||||
bt_set_status_changed_callback(ble_hid->bt, NULL, NULL);
|
||||
bt_disconnect(ble_hid->bt);
|
||||
|
||||
// Wait 2nd core to update nvm storage
|
||||
furi_delay_ms(200);
|
||||
bt_keys_storage_set_default_path(ble_hid->bt);
|
||||
|
||||
furi_check(bt_profile_restore_default(ble_hid->bt));
|
||||
furi_record_close(RECORD_BT);
|
||||
free(ble_hid);
|
||||
}
|
||||
|
||||
void hid_ble_set_state_callback(void* inst, HidStateCallback cb, void* context) {
|
||||
BleHidInstance* ble_hid = inst;
|
||||
furi_assert(ble_hid);
|
||||
ble_hid->state_callback = cb;
|
||||
ble_hid->callback_context = context;
|
||||
}
|
||||
|
||||
bool hid_ble_is_connected(void* inst) {
|
||||
BleHidInstance* ble_hid = inst;
|
||||
furi_assert(ble_hid);
|
||||
return ble_hid->is_connected;
|
||||
}
|
||||
|
||||
bool hid_ble_kb_press(void* inst, uint16_t button) {
|
||||
BleHidInstance* ble_hid = inst;
|
||||
furi_assert(ble_hid);
|
||||
return ble_profile_hid_kb_press(ble_hid->profile, button);
|
||||
}
|
||||
|
||||
bool hid_ble_kb_release(void* inst, uint16_t button) {
|
||||
BleHidInstance* ble_hid = inst;
|
||||
furi_assert(ble_hid);
|
||||
return ble_profile_hid_kb_release(ble_hid->profile, button);
|
||||
}
|
||||
|
||||
bool hid_ble_consumer_press(void* inst, uint16_t button) {
|
||||
BleHidInstance* ble_hid = inst;
|
||||
furi_assert(ble_hid);
|
||||
return ble_profile_hid_consumer_key_press(ble_hid->profile, button);
|
||||
}
|
||||
|
||||
bool hid_ble_consumer_release(void* inst, uint16_t button) {
|
||||
BleHidInstance* ble_hid = inst;
|
||||
furi_assert(ble_hid);
|
||||
return ble_profile_hid_consumer_key_release(ble_hid->profile, button);
|
||||
}
|
||||
|
||||
bool hid_ble_release_all(void* inst) {
|
||||
BleHidInstance* ble_hid = inst;
|
||||
furi_assert(ble_hid);
|
||||
bool state = ble_profile_hid_kb_release_all(ble_hid->profile);
|
||||
state &= ble_profile_hid_consumer_key_release_all(ble_hid->profile);
|
||||
return state;
|
||||
}
|
||||
|
||||
uint8_t hid_ble_get_led_state(void* inst) {
|
||||
UNUSED(inst);
|
||||
FURI_LOG_W(TAG, "hid_ble_get_led_state not implemented");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const BadUsbHidApi hid_api_ble = {
|
||||
.init = hid_ble_init,
|
||||
.deinit = hid_ble_deinit,
|
||||
.set_state_callback = hid_ble_set_state_callback,
|
||||
.is_connected = hid_ble_is_connected,
|
||||
|
||||
.kb_press = hid_ble_kb_press,
|
||||
.kb_release = hid_ble_kb_release,
|
||||
.consumer_press = hid_ble_consumer_press,
|
||||
.consumer_release = hid_ble_consumer_release,
|
||||
.release_all = hid_ble_release_all,
|
||||
.get_led_state = hid_ble_get_led_state,
|
||||
};
|
||||
|
||||
const BadUsbHidApi* bad_usb_hid_get_interface(BadUsbHidInterface interface) {
|
||||
if(interface == BadUsbHidInterfaceUsb) {
|
||||
return &hid_api_usb;
|
||||
} else {
|
||||
return &hid_api_ble;
|
||||
}
|
||||
}
|
||||
|
||||
void bad_usb_hid_ble_remove_pairing(void) {
|
||||
Bt* bt = furi_record_open(RECORD_BT);
|
||||
bt_disconnect(bt);
|
||||
|
||||
// Wait 2nd core to update nvm storage
|
||||
furi_delay_ms(200);
|
||||
|
||||
furi_hal_bt_stop_advertising();
|
||||
|
||||
bt_keys_storage_set_storage_path(bt, APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME));
|
||||
bt_forget_bonded_devices(bt);
|
||||
|
||||
// Wait 2nd core to update nvm storage
|
||||
furi_delay_ms(200);
|
||||
bt_keys_storage_set_default_path(bt);
|
||||
|
||||
furi_check(bt_profile_restore_default(bt));
|
||||
furi_record_close(RECORD_BT);
|
||||
const BadUsbHidApi* bad_usb_hid_get_interface() {
|
||||
return &hid_api_usb;
|
||||
}
|
||||
|
||||
@@ -7,11 +7,6 @@ extern "C" {
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
|
||||
typedef enum {
|
||||
BadUsbHidInterfaceUsb,
|
||||
BadUsbHidInterfaceBle,
|
||||
} BadUsbHidInterface;
|
||||
|
||||
typedef struct {
|
||||
void* (*init)(FuriHalUsbHidConfig* hid_cfg);
|
||||
void (*deinit)(void* inst);
|
||||
@@ -26,7 +21,7 @@ typedef struct {
|
||||
uint8_t (*get_led_state)(void* inst);
|
||||
} BadUsbHidApi;
|
||||
|
||||
const BadUsbHidApi* bad_usb_hid_get_interface(BadUsbHidInterface interface);
|
||||
const BadUsbHidApi* bad_usb_hid_get_interface();
|
||||
|
||||
void bad_usb_hid_ble_remove_pairing(void);
|
||||
|
||||
|
||||
@@ -650,7 +650,7 @@ static void bad_usb_script_set_default_keyboard_layout(BadUsbScript* bad_usb) {
|
||||
memcpy(bad_usb->layout, hid_asciimap, MIN(sizeof(hid_asciimap), sizeof(bad_usb->layout)));
|
||||
}
|
||||
|
||||
BadUsbScript* bad_usb_script_open(FuriString* file_path, BadUsbHidInterface interface) {
|
||||
BadUsbScript* bad_usb_script_open(FuriString* file_path) {
|
||||
furi_assert(file_path);
|
||||
|
||||
BadUsbScript* bad_usb = malloc(sizeof(BadUsbScript));
|
||||
@@ -660,7 +660,7 @@ BadUsbScript* bad_usb_script_open(FuriString* file_path, BadUsbHidInterface inte
|
||||
|
||||
bad_usb->st.state = BadUsbStateInit;
|
||||
bad_usb->st.error[0] = '\0';
|
||||
bad_usb->hid = bad_usb_hid_get_interface(interface);
|
||||
bad_usb->hid = bad_usb_hid_get_interface();
|
||||
|
||||
bad_usb->thread = furi_thread_alloc_ex("BadUsbWorker", 2048, bad_usb_worker, bad_usb);
|
||||
furi_thread_start(bad_usb->thread);
|
||||
|
||||
@@ -34,7 +34,7 @@ typedef struct {
|
||||
|
||||
typedef struct BadUsbScript BadUsbScript;
|
||||
|
||||
BadUsbScript* bad_usb_script_open(FuriString* file_path, BadUsbHidInterface interface);
|
||||
BadUsbScript* bad_usb_script_open(FuriString* file_path);
|
||||
|
||||
void bad_usb_script_close(BadUsbScript* bad_usb);
|
||||
|
||||
|
||||
Binary file not shown.
@@ -1,88 +0,0 @@
|
||||
#include "../bad_usb_app_i.h"
|
||||
|
||||
enum SubmenuIndex {
|
||||
ConfigIndexKeyboardLayout,
|
||||
ConfigIndexInterface,
|
||||
ConfigIndexBleUnpair,
|
||||
};
|
||||
|
||||
const char* const interface_mode_text[2] = {
|
||||
"USB",
|
||||
"BLE",
|
||||
};
|
||||
|
||||
void bad_usb_scene_config_select_callback(void* context, uint32_t index) {
|
||||
BadUsbApp* bad_usb = context;
|
||||
if(index != ConfigIndexInterface) {
|
||||
view_dispatcher_send_custom_event(bad_usb->view_dispatcher, index);
|
||||
}
|
||||
}
|
||||
|
||||
void bad_usb_scene_config_interface_callback(VariableItem* item) {
|
||||
BadUsbApp* bad_usb = variable_item_get_context(item);
|
||||
furi_assert(bad_usb);
|
||||
uint8_t index = variable_item_get_current_value_index(item);
|
||||
|
||||
variable_item_set_current_value_text(item, interface_mode_text[index]);
|
||||
bad_usb->interface = index;
|
||||
|
||||
view_dispatcher_send_custom_event(bad_usb->view_dispatcher, ConfigIndexInterface);
|
||||
}
|
||||
|
||||
static void draw_menu(BadUsbApp* bad_usb) {
|
||||
VariableItemList* var_item_list = bad_usb->var_item_list;
|
||||
|
||||
variable_item_list_reset(var_item_list);
|
||||
|
||||
variable_item_list_add(var_item_list, "Keyboard Layout (global)", 0, NULL, NULL);
|
||||
|
||||
VariableItem* item = variable_item_list_add(
|
||||
var_item_list, "Interface", 2, bad_usb_scene_config_interface_callback, bad_usb);
|
||||
if(bad_usb->interface == BadUsbHidInterfaceUsb) {
|
||||
variable_item_set_current_value_index(item, 0);
|
||||
variable_item_set_current_value_text(item, interface_mode_text[0]);
|
||||
} else {
|
||||
variable_item_set_current_value_index(item, 1);
|
||||
variable_item_set_current_value_text(item, interface_mode_text[1]);
|
||||
variable_item_list_add(var_item_list, "Remove Pairing", 0, NULL, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
void bad_usb_scene_config_on_enter(void* context) {
|
||||
BadUsbApp* bad_usb = context;
|
||||
VariableItemList* var_item_list = bad_usb->var_item_list;
|
||||
|
||||
variable_item_list_set_enter_callback(
|
||||
var_item_list, bad_usb_scene_config_select_callback, bad_usb);
|
||||
draw_menu(bad_usb);
|
||||
variable_item_list_set_selected_item(var_item_list, 0);
|
||||
|
||||
view_dispatcher_switch_to_view(bad_usb->view_dispatcher, BadUsbAppViewConfig);
|
||||
}
|
||||
|
||||
bool bad_usb_scene_config_on_event(void* context, SceneManagerEvent event) {
|
||||
BadUsbApp* bad_usb = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
consumed = true;
|
||||
if(event.event == ConfigIndexKeyboardLayout) {
|
||||
scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneConfigLayout);
|
||||
} else if(event.event == ConfigIndexInterface) {
|
||||
draw_menu(bad_usb);
|
||||
} else if(event.event == ConfigIndexBleUnpair) {
|
||||
bad_usb_hid_ble_remove_pairing();
|
||||
} else {
|
||||
furi_crash("Unknown key type");
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void bad_usb_scene_config_on_exit(void* context) {
|
||||
BadUsbApp* bad_usb = context;
|
||||
VariableItemList* var_item_list = bad_usb->var_item_list;
|
||||
|
||||
variable_item_list_reset(var_item_list);
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
ADD_SCENE(bad_usb, file_select, FileSelect)
|
||||
ADD_SCENE(bad_usb, work, Work)
|
||||
ADD_SCENE(bad_usb, error, Error)
|
||||
ADD_SCENE(bad_usb, config, Config)
|
||||
ADD_SCENE(bad_usb, config_layout, ConfigLayout)
|
||||
|
||||
@@ -20,7 +20,7 @@ bool bad_usb_scene_work_on_event(void* context, SceneManagerEvent event) {
|
||||
bad_usb_script_close(app->bad_usb_script);
|
||||
app->bad_usb_script = NULL;
|
||||
|
||||
scene_manager_next_scene(app->scene_manager, BadUsbSceneConfig);
|
||||
scene_manager_next_scene(app->scene_manager, BadUsbSceneConfigLayout);
|
||||
}
|
||||
consumed = true;
|
||||
} else if(event.event == InputKeyOk) {
|
||||
@@ -39,7 +39,7 @@ bool bad_usb_scene_work_on_event(void* context, SceneManagerEvent event) {
|
||||
void bad_usb_scene_work_on_enter(void* context) {
|
||||
BadUsbApp* app = context;
|
||||
|
||||
app->bad_usb_script = bad_usb_script_open(app->file_path, app->interface);
|
||||
app->bad_usb_script = bad_usb_script_open(app->file_path);
|
||||
bad_usb_script_set_keyboard_layout(app->bad_usb_script, app->keyboard_layout);
|
||||
|
||||
FuriString* file_name;
|
||||
|
||||
@@ -47,7 +47,7 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) {
|
||||
if((state == BadUsbStateIdle) || (state == BadUsbStateDone) ||
|
||||
(state == BadUsbStateNotConnected)) {
|
||||
elements_button_center(canvas, "Run");
|
||||
elements_button_left(canvas, "Config");
|
||||
elements_button_left(canvas, "Layout");
|
||||
} else if((state == BadUsbStateRunning) || (state == BadUsbStateDelay)) {
|
||||
elements_button_center(canvas, "Stop");
|
||||
if(!model->pause_wait) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
Filetype: IR library file
|
||||
Version: 1
|
||||
#
|
||||
#
|
||||
# Model: Smart
|
||||
name: Power
|
||||
type: parsed
|
||||
@@ -14,7 +14,7 @@ type: parsed
|
||||
protocol: NECext
|
||||
address: 83 55 00 00
|
||||
command: 90 6F 00 00
|
||||
#
|
||||
#
|
||||
# Model: Epson
|
||||
name: Power
|
||||
type: parsed
|
||||
@@ -82,7 +82,7 @@ type: parsed
|
||||
protocol: NECext
|
||||
address: 00 30 00 00
|
||||
command: 83 7C 00 00
|
||||
#
|
||||
#
|
||||
name: Vol_up
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
@@ -106,13 +106,13 @@ type: parsed
|
||||
protocol: NECext
|
||||
address: 87 4E 00 00
|
||||
command: 29 D6 00 00
|
||||
#
|
||||
#
|
||||
name: Vol_up
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: 87 4E 00 00
|
||||
command: 08 F7 00 00
|
||||
#
|
||||
#
|
||||
name: Vol_dn
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
@@ -360,12 +360,6 @@ protocol: NECext
|
||||
address: 33 00 00 00
|
||||
command: 0B F4 00 00
|
||||
#
|
||||
name: Power
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: 83 55 00 00
|
||||
command: 90 6F 00 00
|
||||
#
|
||||
name: Vol_dn
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
@@ -629,19 +623,19 @@ type: raw
|
||||
frequency: 38000
|
||||
duty_cycle: 0.330000
|
||||
data: 243 27700 170 27632 246 27694 282 27595 307 27497 241 27696 177 27710 164 27644 245 27629 246 27712 174 27638 211 27736 131 27741 306 27504 214 27727 135 27749 132 27761 126 27744 131 27753 127 27764 121 27767 132 27773 307 27577 131 27706 213 27761 129 27759 128 27770 125 27694 213 27751 307 27578 131 27737 131 27745 304 27575 335 27540 124 27752 132 27749 132 27747 134 27757 134 27758 127 27762 131 27748 131 27750 122 27749 130 27748 125 27772 131 27774 136 27762 135 27686 215 27742 131 27749 132 27756 133 27764 126 24073 9255 4460 672 488 618 541 619 541 619 1675 619 1676 618 542 618 542 618 542 618 1676 618 542 618 543 617 1678 616 568 592 1702 592 1702 592 1703 617 543 617 543 617 1677 617 543 617 1678 615 544 616 544 616 544 616 1678 616 1679 615 544 616 1679 615 545 615 1679 615 1679 615 1679 615 40240 9173 2273 591
|
||||
#
|
||||
#
|
||||
name: Vol_up
|
||||
type: raw
|
||||
frequency: 38000
|
||||
duty_cycle: 0.330000
|
||||
data: 219 27658 217 27663 216 27658 216 27634 216 27642 215 27646 217 27662 217 27637 216 27649 216 27649 218 27656 217 27658 215 27640 214 27636 217 27649 216 27644 218 27635 217 27630 215 27645 216 27631 215 27632 216 27650 216 27628 217 27630 214 27627 217 27623 215 27632 215 27641 216 27634 214 27633 215 27648 215 27648 217 27651 215 27635 216 27629 216 27630 216 2021 9254 4461 618 542 618 542 618 542 618 1675 619 1676 618 541 619 541 619 542 618 1677 617 543 617 543 617 1678 616 568 592 1702 592 1702 618 1676 618 542 618 542 618 543 617 1677 617 543 617 544 616 1678 616 544 616 1678 616 1678 616 1678 616 544 616 1678 616 1678 616 544 616 1678 616 40239 9200 2247 617 99930 110 27739 119 27738 123 27750 126 27738 175 27617 214 27716 203 27604 213 27639 217 27631 214 27722 136 27753 119 27736 175 27618 246 27683 177 27619 245 27685 171 55486 244 27693 158 27635 241 27695 170 27693 129 27717 340 27530 113 27757 106 27751 124 27728 172 27707 126 27666 215 27708 123 27733 123
|
||||
#
|
||||
#
|
||||
name: Vol_dn
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: 18 E9 00 00
|
||||
command: 49 B6 00 00
|
||||
#
|
||||
#
|
||||
name: Power
|
||||
type: parsed
|
||||
protocol: NEC
|
||||
@@ -653,13 +647,13 @@ type: parsed
|
||||
protocol: NEC
|
||||
address: 02 00 00 00
|
||||
command: 48 00 00 00
|
||||
#
|
||||
#
|
||||
name: Vol_dn
|
||||
type: parsed
|
||||
protocol: NEC
|
||||
address: 02 00 00 00
|
||||
command: 40 00 00 00
|
||||
#
|
||||
#
|
||||
name: Mute
|
||||
type: parsed
|
||||
protocol: NEC
|
||||
@@ -683,7 +677,7 @@ type: parsed
|
||||
protocol: NECext
|
||||
address: B8 57 00 00
|
||||
command: 1E E1 00 00
|
||||
#
|
||||
#
|
||||
name: Vol_up
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
@@ -701,13 +695,13 @@ type: parsed
|
||||
protocol: NEC
|
||||
address: 32 00 00 00
|
||||
command: 8F 00 00 00
|
||||
#
|
||||
#
|
||||
name: Vol_up
|
||||
type: parsed
|
||||
protocol: NEC
|
||||
address: 32 00 00 00
|
||||
command: 8C 00 00 00
|
||||
#
|
||||
#
|
||||
name: Mute
|
||||
type: raw
|
||||
frequency: 38000
|
||||
@@ -719,43 +713,37 @@ type: parsed
|
||||
protocol: NEC
|
||||
address: 00 00 00 00
|
||||
command: A8 00 00 00
|
||||
#
|
||||
#
|
||||
name: Mute
|
||||
type: parsed
|
||||
protocol: NEC
|
||||
address: 00 00 00 00
|
||||
command: 88 00 00 00
|
||||
#
|
||||
#
|
||||
name: Vol_dn
|
||||
type: parsed
|
||||
protocol: NEC
|
||||
address: 00 00 00 00
|
||||
command: 9C 00 00 00
|
||||
#
|
||||
#
|
||||
name: Vol_up
|
||||
type: parsed
|
||||
protocol: NEC
|
||||
address: 00 00 00 00
|
||||
command: 8C 00 00 00
|
||||
#
|
||||
name: Power
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: 87 45 00 00
|
||||
command: 17 E8 00 00
|
||||
#
|
||||
name: Vol_up
|
||||
type: raw
|
||||
frequency: 38000
|
||||
duty_cycle: 0.330000
|
||||
data: 9064 4354 666 1559 666 1562 662 1586 638 475 636 477 635 477 635 478 635 1590 635 1591 634 478 635 1591 634 478 634 478 635 478 634 1591 635 478 634 1591 634 478 635 478 634 478 635 1591 634 478 634 1591 635 478 634 478 634 1591 634 1591 635 1591 634 478 635 1591 634 478 634 1591 635 40957 9035 2144 634 95483 9047 2155 632 95484 9048 2153 633
|
||||
#
|
||||
#
|
||||
name: Vol_dn
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: 87 45 00 00
|
||||
command: 50 AF 00 00
|
||||
#
|
||||
#
|
||||
name: Mute
|
||||
type: raw
|
||||
frequency: 38000
|
||||
@@ -767,13 +755,13 @@ type: parsed
|
||||
protocol: NECext
|
||||
address: FF FF 00 00
|
||||
command: E8 17 00 00
|
||||
#
|
||||
#
|
||||
name: Vol_up
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: FF FF 00 00
|
||||
command: BD 42 00 00
|
||||
#
|
||||
#
|
||||
name: Vol_dn
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
@@ -785,13 +773,13 @@ type: parsed
|
||||
protocol: Kaseikyo
|
||||
address: 41 54 32 00
|
||||
command: 05 00 00 00
|
||||
#
|
||||
#
|
||||
name: Vol_up
|
||||
type: parsed
|
||||
protocol: Kaseikyo
|
||||
address: 41 54 32 00
|
||||
command: 70 01 00 00
|
||||
#
|
||||
#
|
||||
name: Vol_dn
|
||||
type: parsed
|
||||
protocol: Kaseikyo
|
||||
@@ -810,6 +798,8 @@ protocol: NECext
|
||||
address: 83 F4 00 00
|
||||
command: 17 E8 00 00
|
||||
#
|
||||
# Model: ViewSonic X1_Projector
|
||||
#
|
||||
name: Vol_up
|
||||
type: raw
|
||||
frequency: 38000
|
||||
@@ -851,3 +841,351 @@ type: raw
|
||||
frequency: 38000
|
||||
duty_cycle: 0.330000
|
||||
data: 9010 4253 564 566 566 1671 566 1699 565 565 539 568 564 566 565 539 566 1699 565 1672 565 566 566 1672 564 567 565 1672 565 567 565 567 564 541 564 1698 566 539 565 567 565 567 562 542 565 1699 564 539 567 1699 565 540 564 1698 566 1672 565 1698 566 1672 565 567 565 1671 565 566 566
|
||||
#
|
||||
# Model: Apeman LC650_
|
||||
#
|
||||
name: Power
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: 00 BD 00 00
|
||||
command: 01 FE 00 00
|
||||
#
|
||||
name: Mute
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: 00 BD 00 00
|
||||
command: 6A 95 00 00
|
||||
#
|
||||
# Model: BenQ MH856UST
|
||||
#
|
||||
name: Vol_up
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: 00 30 00 00
|
||||
command: 97 68 00 00
|
||||
#
|
||||
# Model: BenQ TRY01
|
||||
#
|
||||
name: Power
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: 04 B1 00 00
|
||||
command: 58 A7 00 00
|
||||
#
|
||||
# Model: Generic Universal_Remote
|
||||
#
|
||||
name: Power
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: 48 50 00 00
|
||||
command: 02 FD 00 00
|
||||
#
|
||||
name: Mute
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: 48 50 00 00
|
||||
command: 27 D8 00 00
|
||||
#
|
||||
# Model: Coolux X3S
|
||||
#
|
||||
name: Power
|
||||
type: parsed
|
||||
protocol: NEC
|
||||
address: 01 00 00 00
|
||||
command: 00 00 00 00
|
||||
#
|
||||
name: Mute
|
||||
type: parsed
|
||||
protocol: NEC
|
||||
address: 01 00 00 00
|
||||
command: 1A 00 00 00
|
||||
#
|
||||
# Model: Dell projector
|
||||
#
|
||||
name: Power
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: 4F 50 00 00
|
||||
command: 02 FD 00 00
|
||||
#
|
||||
# Model: Dell tsfm_ir01
|
||||
#
|
||||
name: Mute
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: 4F 50 00 00
|
||||
command: 0F F0 00 00
|
||||
#
|
||||
# Model: Epson 4650
|
||||
#
|
||||
name: Power
|
||||
type: raw
|
||||
frequency: 38000
|
||||
duty_cycle: 0.330000
|
||||
data: 2288 611 571 587 1153 587 572 587 572 1167 572 1168 571 587 573 587 572 587 572 589 570 587 572 587 572 587 572 588 571 77346 2287 611 571 588 1152 588 571 588 571 1168 571 1168 571 588 571 588 571 588 571 589 570 587 572 588 572 587 572 587 572
|
||||
#
|
||||
name: Vol_up
|
||||
type: raw
|
||||
frequency: 38000
|
||||
duty_cycle: 0.330000
|
||||
data: 2288 610 572 588 1151 587 573 588 571 1169 570 586 573 587 572 587 1152 587 573 586 1153 588 572 587 573 586 573 587 572 76771 2289 610 572 588 1151 587 573 587 572 1166 573 587 572 587 572 587 1152 588 571 588 1151 587 573 587 572 587 573 588 571
|
||||
#
|
||||
# Model: Epson EHTW5650
|
||||
#
|
||||
name: Mute
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: 83 55 00 00
|
||||
command: AD 52 00 00
|
||||
#
|
||||
# Model: Epson EMP822H
|
||||
#
|
||||
name: Vol_up
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: 83 55 00 00
|
||||
command: B1 4E 00 00
|
||||
#
|
||||
# Model: Epson projector_Power_Only
|
||||
#
|
||||
name: Power
|
||||
type: raw
|
||||
frequency: 38000
|
||||
duty_cycle: 0.330000
|
||||
data: 8811 4222 530 1580 531 1579 531 507 531 507 531 507 531 508 531 508 530 1582 528 1583 527 535 503 1608 502 536 501 1609 501 537 501 1610 500 538 500 1611 499 538 500 539 500 538 500 1611 500 539 499 538 500 1611 499 539 499 1611 499 1611 500 1611 499 539 499 1611 500 1611 500 539 499 35437 8784 4252 500 1611 500 1612 500 539 500 539 500 539 500 539 500 539 500 1611 500 1612 499 539 500 1612 500 539 500 1612 499 539 500 1612 500 539 500 1612 499 539 500 539 500 539 499 1612 499 540 499 539 500 1612 499 539 500 1612 499 1613 499 1612 499 539 500 1612 500 1612 500 539 500
|
||||
#
|
||||
# Model: Gateway 210_projextor
|
||||
#
|
||||
name: Power
|
||||
type: parsed
|
||||
protocol: NEC
|
||||
address: 30 00 00 00
|
||||
command: 0B 00 00 00
|
||||
#
|
||||
# Model: Groview
|
||||
#
|
||||
name: Power
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: 86 6B 00 00
|
||||
command: 0A F5 00 00
|
||||
#
|
||||
name: Mute
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: 86 6B 00 00
|
||||
command: 4A B5 00 00
|
||||
#
|
||||
name: Vol_up
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: 86 6B 00 00
|
||||
command: 0E F1 00 00
|
||||
#
|
||||
# Model: Infocus Navigator_3
|
||||
#
|
||||
name: Power
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: 87 4E 00 00
|
||||
command: 17 E8 00 00
|
||||
#
|
||||
# Model: JVC LX-UH1B
|
||||
#
|
||||
name: Power
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: 00 6A 00 00
|
||||
command: 40 BF 00 00
|
||||
#
|
||||
# Model: LG PH300-NA
|
||||
#
|
||||
name: Power
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: 04 0F 00 00
|
||||
command: AD 52 00 00
|
||||
#
|
||||
name: Vol_up
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: 04 0F 00 00
|
||||
command: 02 FD 00 00
|
||||
#
|
||||
name: Vol_dn
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: 04 0F 00 00
|
||||
command: 03 FC 00 00
|
||||
#
|
||||
name: Mute
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: 04 0F 00 00
|
||||
command: 09 F6 00 00
|
||||
#
|
||||
# Model: Maxell MC-EU5001
|
||||
#
|
||||
name: Mute
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: 87 45 00 00
|
||||
command: 52 AD 00 00
|
||||
#
|
||||
# Model: NexiGo-PJ20
|
||||
#
|
||||
name: Mute
|
||||
type: parsed
|
||||
protocol: NEC
|
||||
address: 03 00 00 00
|
||||
command: 02 00 00 00
|
||||
#
|
||||
# Model: Optoma projector
|
||||
#
|
||||
name: Vol_up
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: 4F 50 00 00
|
||||
command: 07 F8 00 00
|
||||
#
|
||||
# Model: Optoma Remote_HOF04K276D6
|
||||
#
|
||||
name: Vol_dn
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: 4F 50 00 00
|
||||
command: 0A F5 00 00
|
||||
#
|
||||
# Model: Optoma UHZ45
|
||||
#
|
||||
name: Mute
|
||||
type: parsed
|
||||
protocol: NEC
|
||||
address: 32 00 00 00
|
||||
command: 03 00 00 00
|
||||
#
|
||||
name: Vol_up
|
||||
type: parsed
|
||||
protocol: NEC
|
||||
address: 32 00 00 00
|
||||
command: 09 00 00 00
|
||||
#
|
||||
name: Vol_dn
|
||||
type: parsed
|
||||
protocol: NEC
|
||||
address: 32 00 00 00
|
||||
command: 0C 00 00 00
|
||||
#
|
||||
# Model: Philips PicoPix_Max_PPX620_Projector
|
||||
#
|
||||
name: Power
|
||||
type: parsed
|
||||
protocol: NEC
|
||||
address: 02 00 00 00
|
||||
command: 12 00 00 00
|
||||
#
|
||||
# Model: PVO YG300Pro
|
||||
#
|
||||
name: Power
|
||||
type: parsed
|
||||
protocol: NEC
|
||||
address: 01 00 00 00
|
||||
command: 40 00 00 00
|
||||
#
|
||||
name: Mute
|
||||
type: raw
|
||||
frequency: 38000
|
||||
duty_cycle: 0.330000
|
||||
data: 9107 4376 681 1573 681 472 655 472 654 474 652 475 652 476 651 476 652 476 651 476 651 1604 651 1604 651 1604 651 1603 652 1604 651 1604 651 1604 651 1604 650 476 651 477 650 477 650 476 651 477 651 1604 650 476 651 477 650 1604 651 1604 650 1604 651 1604 650 1604 651 477 650 1604 650 39498 9079 2178 651
|
||||
#
|
||||
# Model: RIF6-cube-projector-raw
|
||||
#
|
||||
name: Power
|
||||
type: raw
|
||||
frequency: 36045
|
||||
duty_cycle: 0.330000
|
||||
data: 9024 4506 570 582 542 582 518 606 518 606 518 606 518 606 518 606 518 610 518 1698 548 1698 546 1700 546 1700 546 1698 546 1700 546 1696 548 1702 570 1674 546 610 516 1698 546 606 542 582 542 582 544 1674 546 610 520 606 542 1674 570 582 542 1676 546 1698 570 1674 572 582 520 1698 546
|
||||
#
|
||||
name: Mute
|
||||
type: raw
|
||||
frequency: 36045
|
||||
duty_cycle: 0.330000
|
||||
data: 9044 4484 572 580 544 580 544 580 544 580 542 582 544 580 520 604 542 586 544 1674 570 1674 570 1676 570 1674 572 1672 570 1674 546 1700 568 1678 570 582 542 1672 572 580 542 580 542 1674 566 582 542 1672 570 584 538 1674 564 580 534 1674 556 1674 556 580 530 1672 558 582 524 1676 552
|
||||
#
|
||||
# Model: Samsung Freestyle_Gen2
|
||||
#
|
||||
name: Power
|
||||
type: parsed
|
||||
protocol: Samsung32
|
||||
address: 07 00 00 00
|
||||
command: 02 00 00 00
|
||||
#
|
||||
name: Mute
|
||||
type: parsed
|
||||
protocol: Samsung32
|
||||
address: 07 00 00 00
|
||||
command: D1 00 00 00
|
||||
#
|
||||
# Model: Samsung VG-TM2360E
|
||||
#
|
||||
name: Power
|
||||
type: raw
|
||||
frequency: 38000
|
||||
duty_cycle: 0.330000
|
||||
data: 1221 1171 433 566 433 881 433 2381 433 1486 434 565 434 1486 433 1776 433 2380 433 565 434 2381 433 1170 434 87358 1220 1171 433 566 433 883 431 2381 433 1486 433 567 432 1487 432 1775 434 2381 432 566 433 2380 434 1171 433 86252 1221 1172 432 565 434 880 435 2381 433 1486 433 565 434 1487 432 1776 433 2380 434 566 433 2379 434 1170 434
|
||||
#
|
||||
name: Vol_up
|
||||
type: raw
|
||||
frequency: 38000
|
||||
duty_cycle: 0.330000
|
||||
data: 1221 1173 431 566 433 882 432 2382 432 1487 432 566 433 565 434 566 433 2671 432 2670 433 2381 433 566 433 87411 324 937 325 358 325 647 326
|
||||
#
|
||||
name: Vol_dn
|
||||
type: raw
|
||||
frequency: 38000
|
||||
duty_cycle: 0.330000
|
||||
data: 1221 1172 432 566 433 882 433 2381 432 1486 434 566 433 565 434 882 433 1486 434 2669 434 2065 433 566 433 87779 324 936 326 358 325 647 326
|
||||
#
|
||||
# Model: Sharp RRMCGA664WJSA_Notevision XR-32S-L
|
||||
#
|
||||
name: Power
|
||||
type: raw
|
||||
frequency: 38000
|
||||
duty_cycle: 0.330000
|
||||
data: 293 1801 296 753 295 1801 296 1801 296 752 296 754 294 1801 296 1800 297 752 296 1802 295 752 296 1801 296 753 295 1800 297 752 296 42709 296 1800 297 753 295 1800 297 1800 297 753 295 1802 295 753 295 753 295 1801 296 753 295 1801 296 754 294 1802 295 753 295 1801 296 42694 295 1800 297 752 296 1803 294 1803 294 753 295 753 295 1801 296 1802 295 752 296 1802 295 752 296 1801 296 753 295 1802 295 753 295 42709 295 1802 295 753 295 1803 294 1801 296 753 295 1802 295 752 296 752 296 1801 296 752 296 1803 294 754 294 1803 294 754 294 1804 293 42694 294 1802 294 755 293 1803 294 1804 268 779 269 779 269 1828 269 1828 269 780 268 1829 268 778 270 1829 323 725 268 1829 268 781 324
|
||||
#
|
||||
# Model: SMART Projectors
|
||||
#
|
||||
name: Power
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: 8B CA 00 00
|
||||
command: 12 ED 00 00
|
||||
#
|
||||
name: Mute
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: 8B CA 00 00
|
||||
command: 11 EE 00 00
|
||||
#
|
||||
# Model: Sony RM_PJ27
|
||||
#
|
||||
name: Vol_up
|
||||
type: parsed
|
||||
protocol: SIRC15
|
||||
address: 54 00 00 00
|
||||
command: 12 00 00 00
|
||||
#
|
||||
name: Vol_dn
|
||||
type: parsed
|
||||
protocol: SIRC15
|
||||
address: 54 00 00 00
|
||||
command: 13 00 00 00
|
||||
#
|
||||
# Model: TopVision
|
||||
#
|
||||
name: Vol_up
|
||||
type: parsed
|
||||
protocol: NEC
|
||||
address: 02 00 00 00
|
||||
command: 11 00 00 00
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,6 +2,20 @@
|
||||
|
||||
#define TAG "Mosgortrans"
|
||||
|
||||
void render_section_header(
|
||||
FuriString* str,
|
||||
const char* name,
|
||||
uint8_t prefix_separator_cnt,
|
||||
uint8_t suffix_separator_cnt) {
|
||||
for(uint8_t i = 0; i < prefix_separator_cnt; i++) {
|
||||
furi_string_cat_printf(str, ":");
|
||||
}
|
||||
furi_string_cat_printf(str, "[ %s ]", name);
|
||||
for(uint8_t i = 0; i < suffix_separator_cnt; i++) {
|
||||
furi_string_cat_printf(str, ":");
|
||||
}
|
||||
}
|
||||
|
||||
void from_days_to_datetime(uint32_t days, DateTime* datetime, uint16_t start_year) {
|
||||
uint32_t timestamp = days * 24 * 60 * 60;
|
||||
DateTime start_datetime = {0};
|
||||
|
||||
@@ -10,6 +10,11 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void render_section_header(
|
||||
FuriString* str,
|
||||
const char* name,
|
||||
uint8_t prefix_separator_cnt,
|
||||
uint8_t suffix_separator_cnt);
|
||||
bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString* result);
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -15,4 +15,11 @@ static constexpr auto nfc_app_api_table = sort(create_array_t<sym_entry>(
|
||||
API_METHOD(
|
||||
mosgortrans_parse_transport_block,
|
||||
bool,
|
||||
(const MfClassicBlock* block, FuriString* result))));
|
||||
(const MfClassicBlock* block, FuriString* result)),
|
||||
API_METHOD(
|
||||
render_section_header,
|
||||
void,
|
||||
(FuriString * str,
|
||||
const char* name,
|
||||
uint8_t prefix_separator_cnt,
|
||||
uint8_t suffix_separator_cnt))));
|
||||
|
||||
@@ -92,6 +92,15 @@ App(
|
||||
sources=["plugins/supported_cards/troika.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="social_moscow_parser",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="social_moscow_plugin_ep",
|
||||
targets=["f7"],
|
||||
requires=["nfc"],
|
||||
sources=["plugins/supported_cards/social_moscow.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="washcity_parser",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
|
||||
@@ -559,6 +559,7 @@ static void nfc_protocol_support_scene_save_name_on_exit(NfcApp* instance) {
|
||||
*/
|
||||
enum {
|
||||
NfcSceneEmulateStateWidget, /**< Widget view is displayed. */
|
||||
NfcSceneEmulateStateWidgetLog, /**< Widget view with Log button is displayed */
|
||||
NfcSceneEmulateStateTextBox, /**< TextBox view is displayed. */
|
||||
};
|
||||
|
||||
@@ -633,12 +634,14 @@ static bool
|
||||
"Log",
|
||||
nfc_protocol_support_common_widget_callback,
|
||||
instance);
|
||||
scene_manager_set_scene_state(
|
||||
instance->scene_manager, NfcSceneEmulate, NfcSceneEmulateStateWidgetLog);
|
||||
}
|
||||
// Update TextBox data
|
||||
text_box_set_text(instance->text_box, furi_string_get_cstr(instance->text_box_store));
|
||||
consumed = true;
|
||||
} else if(event.event == GuiButtonTypeCenter) {
|
||||
if(state == NfcSceneEmulateStateWidget) {
|
||||
if(state == NfcSceneEmulateStateWidgetLog) {
|
||||
view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewTextBox);
|
||||
scene_manager_set_scene_state(
|
||||
instance->scene_manager, NfcSceneEmulate, NfcSceneEmulateStateTextBox);
|
||||
@@ -649,7 +652,7 @@ static bool
|
||||
if(state == NfcSceneEmulateStateTextBox) {
|
||||
view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget);
|
||||
scene_manager_set_scene_state(
|
||||
instance->scene_manager, NfcSceneEmulate, NfcSceneEmulateStateWidget);
|
||||
instance->scene_manager, NfcSceneEmulate, NfcSceneEmulateStateWidgetLog);
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ static const IdMapping bart_zones[] = {
|
||||
{.id = 0x0023, .name = "South Hayward"},
|
||||
{.id = 0x0024, .name = "Union City"},
|
||||
{.id = 0x0025, .name = "Fremont"},
|
||||
{.id = 0x0026, .name = "Daly City(2)?"},
|
||||
{.id = 0x0026, .name = "Castro Valley"},
|
||||
{.id = 0x0027, .name = "Dublin/Pleasanton"},
|
||||
{.id = 0x0028, .name = "South San Francisco"},
|
||||
{.id = 0x0029, .name = "San Bruno"},
|
||||
@@ -115,6 +115,8 @@ static const IdMapping bart_zones[] = {
|
||||
{.id = 0x002c, .name = "West Dublin/Pleasanton"},
|
||||
{.id = 0x002d, .name = "OAK Airport"},
|
||||
{.id = 0x002e, .name = "Warm Springs/South Fremont"},
|
||||
{.id = 0x002f, .name = "Milpitas"},
|
||||
{.id = 0x0030, .name = "Berryessa/North San Jose"},
|
||||
};
|
||||
static const size_t kNumBARTZones = COUNT(bart_zones);
|
||||
|
||||
|
||||
@@ -4,10 +4,21 @@
|
||||
|
||||
#include <nfc/nfc_device.h>
|
||||
#include <bit_lib/bit_lib.h>
|
||||
#include <datetime.h>
|
||||
#include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
|
||||
|
||||
#define TAG "Plantain"
|
||||
|
||||
void from_minutes_to_datetime(uint32_t minutes, DateTime* datetime, uint16_t start_year) {
|
||||
uint32_t timestamp = minutes * 60;
|
||||
DateTime start_datetime = {0};
|
||||
start_datetime.year = start_year - 1;
|
||||
start_datetime.month = 12;
|
||||
start_datetime.day = 31;
|
||||
timestamp += datetime_datetime_to_timestamp(&start_datetime);
|
||||
datetime_timestamp_to_datetime(timestamp, datetime);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
uint64_t a;
|
||||
uint64_t b;
|
||||
@@ -208,29 +219,92 @@ static bool plantain_parse(const NfcDevice* device, FuriString* parsed_data) {
|
||||
bit_lib_bytes_to_num_be(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data));
|
||||
if(key != cfg.keys[cfg.data_sector].a) break;
|
||||
|
||||
// Point to block 0 of sector 4, value 0
|
||||
const uint8_t* temp_ptr = data->block[16].data;
|
||||
// Read first 4 bytes of block 0 of sector 4 from last to first and convert them to uint32_t
|
||||
// 38 18 00 00 becomes 00 00 18 38, and equals to 6200 decimal
|
||||
uint32_t balance =
|
||||
((temp_ptr[3] << 24) | (temp_ptr[2] << 16) | (temp_ptr[1] << 8) | temp_ptr[0]) / 100;
|
||||
// Read card number
|
||||
// Point to block 0 of sector 0, value 0
|
||||
temp_ptr = data->block[0].data;
|
||||
// Read first 7 bytes of block 0 of sector 0 from last to first and convert them to uint64_t
|
||||
// 04 31 16 8A 23 5C 80 becomes 80 5C 23 8A 16 31 04, and equals to 36130104729284868 decimal
|
||||
uint8_t card_number_arr[7];
|
||||
for(size_t i = 0; i < 7; i++) {
|
||||
card_number_arr[i] = temp_ptr[6 - i];
|
||||
}
|
||||
// Copy card number to uint64_t
|
||||
furi_string_printf(parsed_data, "\e#Plantain card\n");
|
||||
uint64_t card_number = 0;
|
||||
for(size_t i = 0; i < 7; i++) {
|
||||
card_number = (card_number << 8) | card_number_arr[i];
|
||||
card_number = (card_number << 8) | data->block[0].data[6 - i];
|
||||
}
|
||||
|
||||
furi_string_printf(
|
||||
parsed_data, "\e#Plantain\nNo.: %lluX\nBalance: %lu\n", card_number, balance);
|
||||
// Print card number with 4-digit groups
|
||||
furi_string_cat_printf(parsed_data, "Number: ");
|
||||
FuriString* card_number_s = furi_string_alloc();
|
||||
furi_string_cat_printf(card_number_s, "%llu", card_number);
|
||||
FuriString* tmp_s = furi_string_alloc_set_str("9643 3078 ");
|
||||
for(uint8_t i = 0; i < 24; i += 4) {
|
||||
for(uint8_t j = 0; j < 4; j++) {
|
||||
furi_string_push_back(tmp_s, furi_string_get_char(card_number_s, i + j));
|
||||
}
|
||||
furi_string_push_back(tmp_s, ' ');
|
||||
}
|
||||
furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(tmp_s));
|
||||
if(data->type == MfClassicType1k) {
|
||||
//balance
|
||||
uint32_t balance = 0;
|
||||
for(uint8_t i = 0; i < 4; i++) {
|
||||
balance = (balance << 8) | data->block[16].data[3 - i];
|
||||
}
|
||||
furi_string_cat_printf(parsed_data, "Balance: %ld rub\n", balance / 100);
|
||||
|
||||
//trips
|
||||
uint8_t trips_metro = data->block[21].data[0];
|
||||
uint8_t trips_ground = data->block[21].data[1];
|
||||
furi_string_cat_printf(parsed_data, "Trips: %d\n", trips_metro + trips_ground);
|
||||
//trip time
|
||||
uint32_t last_trip_timestamp = 0;
|
||||
for(uint8_t i = 0; i < 3; i++) {
|
||||
last_trip_timestamp = (last_trip_timestamp << 8) | data->block[21].data[4 - i];
|
||||
}
|
||||
DateTime last_trip = {0};
|
||||
from_minutes_to_datetime(last_trip_timestamp + 24 * 60, &last_trip, 2010);
|
||||
furi_string_cat_printf(
|
||||
parsed_data,
|
||||
"Trip start: %02d.%02d.%04d %02d:%02d\n",
|
||||
last_trip.day,
|
||||
last_trip.month,
|
||||
last_trip.year,
|
||||
last_trip.hour,
|
||||
last_trip.minute);
|
||||
//validator
|
||||
uint16_t validator = (data->block[20].data[5] << 8) | data->block[20].data[4];
|
||||
furi_string_cat_printf(parsed_data, "Validator: %d\n", validator);
|
||||
//tariff
|
||||
uint16_t fare = (data->block[20].data[7] << 8) | data->block[20].data[6];
|
||||
furi_string_cat_printf(parsed_data, "Tariff: %d rub\n", fare / 100);
|
||||
//trips in metro
|
||||
furi_string_cat_printf(parsed_data, "Trips (Metro): %d\n", trips_metro);
|
||||
//trips on ground
|
||||
furi_string_cat_printf(parsed_data, "Trips (Ground): %d\n", trips_ground);
|
||||
//last payment
|
||||
uint32_t last_payment_timestamp = 0;
|
||||
for(uint8_t i = 0; i < 3; i++) {
|
||||
last_payment_timestamp = (last_payment_timestamp << 8) |
|
||||
data->block[18].data[4 - i];
|
||||
}
|
||||
DateTime last_payment_date = {0};
|
||||
from_minutes_to_datetime(last_payment_timestamp + 24 * 60, &last_payment_date, 2010);
|
||||
furi_string_cat_printf(
|
||||
parsed_data,
|
||||
"Last pay: %02d.%02d.%04d %02d:%02d\n",
|
||||
last_payment_date.day,
|
||||
last_payment_date.month,
|
||||
last_payment_date.year,
|
||||
last_payment_date.hour,
|
||||
last_payment_date.minute);
|
||||
//payment summ
|
||||
uint16_t last_payment = (data->block[18].data[9] << 8) | data->block[18].data[8];
|
||||
furi_string_cat_printf(parsed_data, "Amount: %d rub", last_payment / 100);
|
||||
furi_string_free(card_number_s);
|
||||
furi_string_free(tmp_s);
|
||||
} else if(data->type == MfClassicType4k) {
|
||||
//trips
|
||||
uint8_t trips_metro = data->block[36].data[0];
|
||||
uint8_t trips_ground = data->block[36].data[1];
|
||||
furi_string_cat_printf(parsed_data, "Trips: %d\n", trips_metro + trips_ground);
|
||||
//trips in metro
|
||||
furi_string_cat_printf(parsed_data, "Trips (Metro): %d\n", trips_metro);
|
||||
//trips on ground
|
||||
furi_string_cat_printf(parsed_data, "Trips (Ground): %d\n", trips_ground);
|
||||
}
|
||||
parsed = true;
|
||||
} while(false);
|
||||
|
||||
|
||||
301
applications/main/nfc/plugins/supported_cards/social_moscow.c
Normal file
301
applications/main/nfc/plugins/supported_cards/social_moscow.c
Normal file
@@ -0,0 +1,301 @@
|
||||
#include "nfc_supported_card_plugin.h"
|
||||
#include <core/check.h>
|
||||
|
||||
#include <flipper_application/flipper_application.h>
|
||||
|
||||
#include <nfc/nfc_device.h>
|
||||
#include <bit_lib/bit_lib.h>
|
||||
#include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
|
||||
#include "../../api/mosgortrans/mosgortrans_util.h"
|
||||
#include "furi_hal_rtc.h"
|
||||
|
||||
#define TAG "Social_Moscow"
|
||||
|
||||
typedef struct {
|
||||
uint64_t a;
|
||||
uint64_t b;
|
||||
} MfClassicKeyPair;
|
||||
|
||||
typedef struct {
|
||||
const MfClassicKeyPair* keys;
|
||||
uint32_t data_sector;
|
||||
} SocialMoscowCardConfig;
|
||||
|
||||
static const MfClassicKeyPair social_moscow_1k_keys[] = {
|
||||
{.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025},
|
||||
{.a = 0x2735fc181807, .b = 0xbf23a53c1f63},
|
||||
{.a = 0x2aba9519f574, .b = 0xcb9a1f2d7368},
|
||||
{.a = 0x84fd7f7a12b6, .b = 0xc7c0adb3284f},
|
||||
{.a = 0x73068f118c13, .b = 0x2b7f3253fac5},
|
||||
{.a = 0x186d8c4b93f9, .b = 0x9f131d8c2057},
|
||||
{.a = 0x3a4bba8adaf0, .b = 0x67362d90f973},
|
||||
{.a = 0x8765b17968a2, .b = 0x6202a38f69e2},
|
||||
{.a = 0x40ead80721ce, .b = 0x100533b89331},
|
||||
{.a = 0x0db5e6523f7c, .b = 0x653a87594079},
|
||||
{.a = 0x51119dae5216, .b = 0xd8a274b2e026},
|
||||
{.a = 0x51119dae5216, .b = 0xd8a274b2e026},
|
||||
{.a = 0x51119dae5216, .b = 0xd8a274b2e026},
|
||||
{.a = 0x2aba9519f574, .b = 0xcb9a1f2d7368},
|
||||
{.a = 0x84fd7f7a12b6, .b = 0xc7c0adb3284f},
|
||||
{.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}};
|
||||
|
||||
static const MfClassicKeyPair social_moscow_4k_keys[] = {
|
||||
{.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}, //1
|
||||
{.a = 0x2735fc181807, .b = 0xbf23a53c1f63}, //2
|
||||
{.a = 0x2aba9519f574, .b = 0xcb9a1f2d7368}, //3
|
||||
{.a = 0x84fd7f7a12b6, .b = 0xc7c0adb3284f}, //4
|
||||
{.a = 0x73068f118c13, .b = 0x2b7f3253fac5}, //5
|
||||
{.a = 0x186d8c4b93f9, .b = 0x9f131d8c2057}, //6
|
||||
{.a = 0x3a4bba8adaf0, .b = 0x67362d90f973}, //7
|
||||
{.a = 0x8765b17968a2, .b = 0x6202a38f69e2}, //8
|
||||
{.a = 0x40ead80721ce, .b = 0x100533b89331}, //9
|
||||
{.a = 0x0db5e6523f7c, .b = 0x653a87594079}, //10
|
||||
{.a = 0x51119dae5216, .b = 0xd8a274b2e026}, //11
|
||||
{.a = 0x51119dae5216, .b = 0xd8a274b2e026}, //12
|
||||
{.a = 0x51119dae5216, .b = 0xd8a274b2e026}, //13
|
||||
{.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}, //14
|
||||
{.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}, //15
|
||||
{.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}, //16
|
||||
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //17
|
||||
{.a = 0x2aba9519f574, .b = 0xcb9a1f2d7368}, //18
|
||||
{.a = 0x84fd7f7a12b6, .b = 0xc7c0adb3284f}, //19
|
||||
{.a = 0x2aba9519f574, .b = 0xcb9a1f2d7368}, //20
|
||||
{.a = 0x84fd7f7a12b6, .b = 0xc7c0adb3284f}, //21
|
||||
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //22
|
||||
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //23
|
||||
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //24
|
||||
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //25
|
||||
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //26
|
||||
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //27
|
||||
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //28
|
||||
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //29
|
||||
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //30
|
||||
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //31
|
||||
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //32
|
||||
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //33
|
||||
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //34
|
||||
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //35
|
||||
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //36
|
||||
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //37
|
||||
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //38
|
||||
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //39
|
||||
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //40
|
||||
};
|
||||
|
||||
static bool social_moscow_get_card_config(SocialMoscowCardConfig* config, MfClassicType type) {
|
||||
bool success = true;
|
||||
if(type == MfClassicType1k) {
|
||||
config->data_sector = 15;
|
||||
config->keys = social_moscow_1k_keys;
|
||||
} else if(type == MfClassicType4k) {
|
||||
config->data_sector = 15;
|
||||
config->keys = social_moscow_4k_keys;
|
||||
} else {
|
||||
success = false;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
static bool social_moscow_verify_type(Nfc* nfc, MfClassicType type) {
|
||||
bool verified = false;
|
||||
|
||||
do {
|
||||
SocialMoscowCardConfig cfg = {};
|
||||
if(!social_moscow_get_card_config(&cfg, type)) break;
|
||||
|
||||
const uint8_t block_num = mf_classic_get_first_block_num_of_sector(cfg.data_sector);
|
||||
FURI_LOG_D(TAG, "Verifying sector %lu", cfg.data_sector);
|
||||
|
||||
MfClassicKey key = {0};
|
||||
bit_lib_num_to_bytes_be(cfg.keys[cfg.data_sector].a, COUNT_OF(key.data), key.data);
|
||||
|
||||
MfClassicAuthContext auth_context;
|
||||
MfClassicError error =
|
||||
mf_classic_poller_sync_auth(nfc, block_num, &key, MfClassicKeyTypeA, &auth_context);
|
||||
if(error != MfClassicErrorNone) {
|
||||
FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error);
|
||||
break;
|
||||
}
|
||||
FURI_LOG_D(TAG, "Verify success!");
|
||||
verified = true;
|
||||
} while(false);
|
||||
|
||||
return verified;
|
||||
}
|
||||
|
||||
static bool social_moscow_verify(Nfc* nfc) {
|
||||
return social_moscow_verify_type(nfc, MfClassicType1k) ||
|
||||
social_moscow_verify_type(nfc, MfClassicType4k);
|
||||
}
|
||||
|
||||
static bool social_moscow_read(Nfc* nfc, NfcDevice* device) {
|
||||
furi_assert(nfc);
|
||||
furi_assert(device);
|
||||
|
||||
bool is_read = false;
|
||||
|
||||
MfClassicData* data = mf_classic_alloc();
|
||||
nfc_device_copy_data(device, NfcProtocolMfClassic, data);
|
||||
|
||||
do {
|
||||
MfClassicType type = MfClassicType4k;
|
||||
MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type);
|
||||
if(error != MfClassicErrorNone) break;
|
||||
|
||||
data->type = type;
|
||||
SocialMoscowCardConfig cfg = {};
|
||||
if(!social_moscow_get_card_config(&cfg, data->type)) break;
|
||||
|
||||
MfClassicDeviceKeys keys = {};
|
||||
for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) {
|
||||
bit_lib_num_to_bytes_be(cfg.keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data);
|
||||
FURI_BIT_SET(keys.key_a_mask, i);
|
||||
bit_lib_num_to_bytes_be(cfg.keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data);
|
||||
FURI_BIT_SET(keys.key_b_mask, i);
|
||||
}
|
||||
|
||||
error = mf_classic_poller_sync_read(nfc, &keys, data);
|
||||
if(error == MfClassicErrorNotPresent) {
|
||||
FURI_LOG_W(TAG, "Failed to read data");
|
||||
break;
|
||||
}
|
||||
|
||||
nfc_device_set_data(device, NfcProtocolMfClassic, data);
|
||||
|
||||
is_read = (error == MfClassicErrorNone);
|
||||
} while(false);
|
||||
|
||||
mf_classic_free(data);
|
||||
|
||||
return is_read;
|
||||
}
|
||||
|
||||
static uint8_t calculate_luhn(uint64_t number) {
|
||||
// https://en.wikipedia.org/wiki/Luhn_algorithm
|
||||
// Drop existing check digit to form payload
|
||||
uint64_t payload = number / 10;
|
||||
int sum = 0;
|
||||
int position = 0;
|
||||
|
||||
while(payload > 0) {
|
||||
int digit = payload % 10;
|
||||
if(position % 2 == 0) {
|
||||
digit *= 2;
|
||||
}
|
||||
if(digit > 9) {
|
||||
digit = (digit / 10) + (digit % 10);
|
||||
}
|
||||
sum += digit;
|
||||
payload /= 10;
|
||||
position++;
|
||||
}
|
||||
|
||||
return (10 - (sum % 10)) % 10;
|
||||
}
|
||||
|
||||
static uint64_t hex_num(uint64_t hex) {
|
||||
uint64_t result = 0;
|
||||
for(uint8_t i = 0; i < 8; ++i) {
|
||||
uint8_t half_byte = hex & 0x0F;
|
||||
uint64_t num = 0;
|
||||
for(uint8_t j = 0; j < 4; ++j) {
|
||||
num += (half_byte & 0x1) * (1 << j);
|
||||
half_byte = half_byte >> 1;
|
||||
}
|
||||
result += num * pow(10, i);
|
||||
hex = hex >> 4;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool social_moscow_parse(const NfcDevice* device, FuriString* parsed_data) {
|
||||
furi_assert(device);
|
||||
|
||||
const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic);
|
||||
|
||||
bool parsed = false;
|
||||
|
||||
do {
|
||||
// Verify card type
|
||||
SocialMoscowCardConfig cfg = {};
|
||||
if(!social_moscow_get_card_config(&cfg, data->type)) break;
|
||||
|
||||
// Verify key
|
||||
const MfClassicSectorTrailer* sec_tr =
|
||||
mf_classic_get_sector_trailer_by_sector(data, cfg.data_sector);
|
||||
|
||||
const uint64_t key_a =
|
||||
bit_lib_bytes_to_num_be(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data));
|
||||
const uint64_t key_b =
|
||||
bit_lib_bytes_to_num_be(sec_tr->key_b.data, COUNT_OF(sec_tr->key_b.data));
|
||||
if((key_a != cfg.keys[cfg.data_sector].a) || (key_b != cfg.keys[cfg.data_sector].b)) break;
|
||||
|
||||
uint32_t card_code = bit_lib_get_bits_32(data->block[60].data, 8, 24);
|
||||
uint8_t card_region = bit_lib_get_bits(data->block[60].data, 32, 8);
|
||||
uint64_t card_number = bit_lib_get_bits_64(data->block[60].data, 40, 40);
|
||||
uint8_t card_control = bit_lib_get_bits(data->block[60].data, 80, 4);
|
||||
uint64_t omc_number = bit_lib_get_bits_64(data->block[21].data, 8, 64);
|
||||
uint8_t year = data->block[60].data[11];
|
||||
uint8_t month = data->block[60].data[12];
|
||||
|
||||
uint64_t number = hex_num(card_control) + hex_num(card_number) * 10 +
|
||||
hex_num(card_region) * 10 * 10000000000 +
|
||||
hex_num(card_code) * 10 * 10000000000 * 100;
|
||||
|
||||
uint8_t luhn = calculate_luhn(number);
|
||||
if(luhn != card_control) break;
|
||||
|
||||
FuriString* metro_result = furi_string_alloc();
|
||||
FuriString* ground_result = furi_string_alloc();
|
||||
bool is_metro_data_present =
|
||||
mosgortrans_parse_transport_block(&data->block[4], metro_result);
|
||||
bool is_ground_data_present =
|
||||
mosgortrans_parse_transport_block(&data->block[16], ground_result);
|
||||
furi_string_cat_printf(
|
||||
parsed_data,
|
||||
"\e#Social \ecard\nNumber: %lx %x %llx %x\nOMC: %llx\nValid for: %02x/%02x %02x%02x\n",
|
||||
card_code,
|
||||
card_region,
|
||||
card_number,
|
||||
card_control,
|
||||
omc_number,
|
||||
month,
|
||||
year,
|
||||
data->block[60].data[13],
|
||||
data->block[60].data[14]);
|
||||
if(is_metro_data_present && !furi_string_empty(metro_result)) {
|
||||
render_section_header(parsed_data, "Metro", 22, 21);
|
||||
furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(metro_result));
|
||||
}
|
||||
if(is_ground_data_present && !furi_string_empty(ground_result)) {
|
||||
render_section_header(parsed_data, "Ground", 21, 20);
|
||||
furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(ground_result));
|
||||
}
|
||||
furi_string_free(ground_result);
|
||||
furi_string_free(metro_result);
|
||||
parsed = true;
|
||||
} while(false);
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
/* Actual implementation of app<>plugin interface */
|
||||
static const NfcSupportedCardsPlugin social_moscow_plugin = {
|
||||
.protocol = NfcProtocolMfClassic,
|
||||
.verify = social_moscow_verify,
|
||||
.read = social_moscow_read,
|
||||
.parse = social_moscow_parse,
|
||||
};
|
||||
|
||||
/* Plugin descriptor to comply with basic plugin specification */
|
||||
static const FlipperAppPluginDescriptor social_moscow_plugin_descriptor = {
|
||||
.appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID,
|
||||
.ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION,
|
||||
.entry_point = &social_moscow_plugin,
|
||||
};
|
||||
|
||||
/* Plugin entry point - must return a pointer to const descriptor */
|
||||
const FlipperAppPluginDescriptor* social_moscow_plugin_ep() {
|
||||
return &social_moscow_plugin_descriptor;
|
||||
}
|
||||
@@ -82,20 +82,6 @@ static const MfClassicKeyPair troika_4k_keys[] = {
|
||||
{.a = 0xBB52F8CCE07F, .b = 0x6B6119752C70}, //40
|
||||
};
|
||||
|
||||
static void troika_render_section_header(
|
||||
FuriString* str,
|
||||
const char* name,
|
||||
uint8_t prefix_separator_cnt,
|
||||
uint8_t suffix_separator_cnt) {
|
||||
for(uint8_t i = 0; i < prefix_separator_cnt; i++) {
|
||||
furi_string_cat_printf(str, ":");
|
||||
}
|
||||
furi_string_cat_printf(str, "[ %s ]", name);
|
||||
for(uint8_t i = 0; i < suffix_separator_cnt; i++) {
|
||||
furi_string_cat_printf(str, ":");
|
||||
}
|
||||
}
|
||||
|
||||
static bool troika_get_card_config(TroikaCardConfig* config, MfClassicType type) {
|
||||
bool success = true;
|
||||
|
||||
@@ -212,23 +198,25 @@ static bool troika_parse(const NfcDevice* device, FuriString* parsed_data) {
|
||||
FuriString* ground_result = furi_string_alloc();
|
||||
FuriString* tat_result = furi_string_alloc();
|
||||
|
||||
bool result1 = mosgortrans_parse_transport_block(&data->block[32], metro_result);
|
||||
bool result2 = mosgortrans_parse_transport_block(&data->block[28], ground_result);
|
||||
bool result3 = mosgortrans_parse_transport_block(&data->block[16], tat_result);
|
||||
bool is_metro_data_present =
|
||||
mosgortrans_parse_transport_block(&data->block[32], metro_result);
|
||||
bool is_ground_data_present =
|
||||
mosgortrans_parse_transport_block(&data->block[28], ground_result);
|
||||
bool is_tat_data_present = mosgortrans_parse_transport_block(&data->block[16], tat_result);
|
||||
|
||||
furi_string_cat_printf(parsed_data, "\e#Troyka card\n");
|
||||
if(result1 && !furi_string_empty(metro_result)) {
|
||||
troika_render_section_header(parsed_data, "Metro", 22, 21);
|
||||
if(is_metro_data_present && !furi_string_empty(metro_result)) {
|
||||
render_section_header(parsed_data, "Metro", 22, 21);
|
||||
furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(metro_result));
|
||||
}
|
||||
|
||||
if(result2 && !furi_string_empty(ground_result)) {
|
||||
troika_render_section_header(parsed_data, "Ediny", 22, 22);
|
||||
if(is_ground_data_present && !furi_string_empty(ground_result)) {
|
||||
render_section_header(parsed_data, "Ediny", 22, 22);
|
||||
furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(ground_result));
|
||||
}
|
||||
|
||||
if(result3 && !furi_string_empty(tat_result)) {
|
||||
troika_render_section_header(parsed_data, "TAT", 24, 23);
|
||||
if(is_tat_data_present && !furi_string_empty(tat_result)) {
|
||||
render_section_header(parsed_data, "TAT", 24, 23);
|
||||
furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(tat_result));
|
||||
}
|
||||
|
||||
@@ -236,7 +224,7 @@ static bool troika_parse(const NfcDevice* device, FuriString* parsed_data) {
|
||||
furi_string_free(ground_result);
|
||||
furi_string_free(metro_result);
|
||||
|
||||
parsed = result1 || result2 || result3;
|
||||
parsed = is_metro_data_present || is_ground_data_present || is_tat_data_present;
|
||||
} while(false);
|
||||
|
||||
return parsed;
|
||||
|
||||
@@ -999,13 +999,12 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) {
|
||||
chat_event = subghz_chat_worker_get_event_chat(subghz_chat);
|
||||
switch(chat_event.event) {
|
||||
case SubGhzChatEventInputData:
|
||||
if(chat_event.c == CliSymbolAsciiETX) {
|
||||
if(chat_event.c == CliKeyETX) {
|
||||
printf("\r\n");
|
||||
chat_event.event = SubGhzChatEventUserExit;
|
||||
subghz_chat_worker_put_event_chat(subghz_chat, &chat_event);
|
||||
break;
|
||||
} else if(
|
||||
(chat_event.c == CliSymbolAsciiBackspace) || (chat_event.c == CliSymbolAsciiDel)) {
|
||||
} else if((chat_event.c == CliKeyBackspace) || (chat_event.c == CliKeyDEL)) {
|
||||
size_t len = furi_string_utf8_length(input);
|
||||
if(len > furi_string_utf8_length(name)) {
|
||||
printf("%s", "\e[D\e[1P");
|
||||
@@ -1027,7 +1026,7 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) {
|
||||
}
|
||||
furi_string_set(input, sysmsg);
|
||||
}
|
||||
} else if(chat_event.c == CliSymbolAsciiCR) {
|
||||
} else if(chat_event.c == CliKeyCR) {
|
||||
printf("\r\n");
|
||||
furi_string_push_back(input, '\r');
|
||||
furi_string_push_back(input, '\n');
|
||||
@@ -1041,7 +1040,7 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) {
|
||||
furi_string_printf(input, "%s", furi_string_get_cstr(name));
|
||||
printf("%s", furi_string_get_cstr(input));
|
||||
fflush(stdout);
|
||||
} else if(chat_event.c == CliSymbolAsciiLF) {
|
||||
} else if(chat_event.c == CliKeyLF) {
|
||||
//cut out the symbol \n
|
||||
} else {
|
||||
putc(chat_event.c, stdout);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <cli/cli.h>
|
||||
#include <cli/cli_ansi.h>
|
||||
|
||||
void subghz_on_system_start(void);
|
||||
|
||||
@@ -430,13 +430,11 @@ static void bt_change_profile(Bt* bt, BtMessage* message) {
|
||||
*message->profile_instance = NULL;
|
||||
}
|
||||
}
|
||||
if(message->lock) api_lock_unlock(message->lock);
|
||||
}
|
||||
|
||||
static void bt_close_connection(Bt* bt, BtMessage* message) {
|
||||
static void bt_close_connection(Bt* bt) {
|
||||
bt_close_rpc_connection(bt);
|
||||
furi_hal_bt_stop_advertising();
|
||||
if(message->lock) api_lock_unlock(message->lock);
|
||||
}
|
||||
|
||||
static void bt_apply_settings(Bt* bt) {
|
||||
@@ -484,19 +482,13 @@ static void bt_load_settings(Bt* bt) {
|
||||
}
|
||||
|
||||
static void bt_handle_get_settings(Bt* bt, BtMessage* message) {
|
||||
furi_assert(message->lock);
|
||||
*message->data.settings = bt->bt_settings;
|
||||
api_lock_unlock(message->lock);
|
||||
}
|
||||
|
||||
static void bt_handle_set_settings(Bt* bt, BtMessage* message) {
|
||||
furi_assert(message->lock);
|
||||
bt->bt_settings = *message->data.csettings;
|
||||
|
||||
bt_apply_settings(bt);
|
||||
bt_settings_save(&bt->bt_settings);
|
||||
|
||||
api_lock_unlock(message->lock);
|
||||
}
|
||||
|
||||
static void bt_handle_reload_keys_settings(Bt* bt) {
|
||||
@@ -548,6 +540,12 @@ int32_t bt_srv(void* p) {
|
||||
while(1) {
|
||||
furi_check(
|
||||
furi_message_queue_get(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk);
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"call %d, lock 0x%p, result 0x%p",
|
||||
message.type,
|
||||
(void*)message.lock,
|
||||
(void*)message.result);
|
||||
if(message.type == BtMessageTypeUpdateStatus) {
|
||||
// Update view ports
|
||||
bt_statusbar_update(bt);
|
||||
@@ -571,7 +569,7 @@ int32_t bt_srv(void* p) {
|
||||
} else if(message.type == BtMessageTypeSetProfile) {
|
||||
bt_change_profile(bt, &message);
|
||||
} else if(message.type == BtMessageTypeDisconnect) {
|
||||
bt_close_connection(bt, &message);
|
||||
bt_close_connection(bt);
|
||||
} else if(message.type == BtMessageTypeForgetBondedDevices) {
|
||||
bt_keys_storage_delete(bt->keys_storage);
|
||||
} else if(message.type == BtMessageTypeGetSettings) {
|
||||
@@ -581,6 +579,8 @@ int32_t bt_srv(void* p) {
|
||||
} else if(message.type == BtMessageTypeReloadKeysSettings) {
|
||||
bt_handle_reload_keys_settings(bt);
|
||||
}
|
||||
|
||||
if(message.lock) api_lock_unlock(message.lock);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
#include "cli_i.h"
|
||||
#include "cli_commands.h"
|
||||
#include "cli_vcp.h"
|
||||
#include "cli_ansi.h"
|
||||
#include <furi_hal_version.h>
|
||||
#include <loader/loader.h>
|
||||
|
||||
#define TAG "CliSrv"
|
||||
|
||||
#define CLI_INPUT_LEN_LIMIT 256
|
||||
#define CLI_PROMPT ">: " // qFlipper does not recognize us if we use escape sequences :(
|
||||
#define CLI_PROMPT_LENGTH 3 // printable characters
|
||||
|
||||
Cli* cli_alloc(void) {
|
||||
Cli* cli = malloc(sizeof(Cli));
|
||||
@@ -85,7 +88,7 @@ bool cli_cmd_interrupt_received(Cli* cli) {
|
||||
char c = '\0';
|
||||
if(cli_is_connected(cli)) {
|
||||
if(cli->session->rx((uint8_t*)&c, 1, 0) == 1) {
|
||||
return c == CliSymbolAsciiETX;
|
||||
return c == CliKeyETX;
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
@@ -102,7 +105,8 @@ void cli_print_usage(const char* cmd, const char* usage, const char* arg) {
|
||||
}
|
||||
|
||||
void cli_motd(void) {
|
||||
printf("\r\n"
|
||||
printf(ANSI_FLIPPER_BRAND_ORANGE
|
||||
"\r\n"
|
||||
" _.-------.._ -,\r\n"
|
||||
" .-\"```\"--..,,_/ /`-, -, \\ \r\n"
|
||||
" .:\" /:/ /'\\ \\ ,_..., `. | |\r\n"
|
||||
@@ -116,12 +120,11 @@ void cli_motd(void) {
|
||||
" _L_ _ ___ ___ ___ ___ ____--\"`___ _ ___\r\n"
|
||||
"| __|| | |_ _|| _ \\| _ \\| __|| _ \\ / __|| | |_ _|\r\n"
|
||||
"| _| | |__ | | | _/| _/| _| | / | (__ | |__ | |\r\n"
|
||||
"|_| |____||___||_| |_| |___||_|_\\ \\___||____||___|\r\n"
|
||||
"\r\n"
|
||||
"Welcome to Flipper Zero Command Line Interface!\r\n"
|
||||
"|_| |____||___||_| |_| |___||_|_\\ \\___||____||___|\r\n" ANSI_RESET
|
||||
"\r\n" ANSI_FG_BR_WHITE "Welcome to " ANSI_FLIPPER_BRAND_ORANGE
|
||||
"Flipper Zero" ANSI_FG_BR_WHITE " Command Line Interface!\r\n"
|
||||
"Read the manual: https://docs.flipper.net/development/cli\r\n"
|
||||
"Run `help` or `?` to list available commands\r\n"
|
||||
"\r\n");
|
||||
"Run `help` or `?` to list available commands\r\n" ANSI_RESET "\r\n");
|
||||
|
||||
const Version* firmware_version = furi_hal_version_get_firmware_version();
|
||||
if(firmware_version) {
|
||||
@@ -142,7 +145,7 @@ void cli_nl(Cli* cli) {
|
||||
|
||||
void cli_prompt(Cli* cli) {
|
||||
UNUSED(cli);
|
||||
printf("\r\n>: %s", furi_string_get_cstr(cli->line));
|
||||
printf("\r\n" CLI_PROMPT "%s", furi_string_get_cstr(cli->line));
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
@@ -165,7 +168,7 @@ static void cli_handle_backspace(Cli* cli) {
|
||||
|
||||
cli->cursor_position--;
|
||||
} else {
|
||||
cli_putc(cli, CliSymbolAsciiBell);
|
||||
cli_putc(cli, CliKeyBell);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,7 +244,7 @@ static void cli_handle_enter(Cli* cli) {
|
||||
printf(
|
||||
"`%s` command not found, use `help` or `?` to list all available commands",
|
||||
furi_string_get_cstr(command));
|
||||
cli_putc(cli, CliSymbolAsciiBell);
|
||||
cli_putc(cli, CliKeyBell);
|
||||
}
|
||||
|
||||
cli_reset(cli);
|
||||
@@ -305,8 +308,85 @@ static void cli_handle_autocomplete(Cli* cli) {
|
||||
cli_prompt(cli);
|
||||
}
|
||||
|
||||
static void cli_handle_escape(Cli* cli, char c) {
|
||||
if(c == 'A') {
|
||||
typedef enum {
|
||||
CliCharClassWord,
|
||||
CliCharClassSpace,
|
||||
CliCharClassOther,
|
||||
} CliCharClass;
|
||||
|
||||
/**
|
||||
* @brief Determines the class that a character belongs to
|
||||
*
|
||||
* The return value of this function should not be used on its own; it should
|
||||
* only be used for comparing it with other values returned by this function.
|
||||
* This function is used internally in `cli_skip_run`.
|
||||
*/
|
||||
static CliCharClass cli_char_class(char c) {
|
||||
if((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_') {
|
||||
return CliCharClassWord;
|
||||
} else if(c == ' ') {
|
||||
return CliCharClassSpace;
|
||||
} else {
|
||||
return CliCharClassOther;
|
||||
}
|
||||
}
|
||||
|
||||
typedef enum {
|
||||
CliSkipDirectionLeft,
|
||||
CliSkipDirectionRight,
|
||||
} CliSkipDirection;
|
||||
|
||||
/**
|
||||
* @brief Skips a run of a class of characters
|
||||
*
|
||||
* @param string Input string
|
||||
* @param original_pos Position to start the search at
|
||||
* @param direction Direction in which to perform the search
|
||||
* @returns The position at which the run ends
|
||||
*/
|
||||
static size_t cli_skip_run(FuriString* string, size_t original_pos, CliSkipDirection direction) {
|
||||
if(furi_string_size(string) == 0) return original_pos;
|
||||
if(direction == CliSkipDirectionLeft && original_pos == 0) return original_pos;
|
||||
if(direction == CliSkipDirectionRight && original_pos == furi_string_size(string))
|
||||
return original_pos;
|
||||
|
||||
int8_t look_offset = (direction == CliSkipDirectionLeft) ? -1 : 0;
|
||||
int8_t increment = (direction == CliSkipDirectionLeft) ? -1 : 1;
|
||||
int32_t position = original_pos;
|
||||
CliCharClass start_class =
|
||||
cli_char_class(furi_string_get_char(string, position + look_offset));
|
||||
|
||||
while(true) {
|
||||
position += increment;
|
||||
if(position < 0) break;
|
||||
if(position >= (int32_t)furi_string_size(string)) break;
|
||||
if(cli_char_class(furi_string_get_char(string, position + look_offset)) != start_class)
|
||||
break;
|
||||
}
|
||||
|
||||
return MAX(0, position);
|
||||
}
|
||||
|
||||
void cli_process_input(Cli* cli) {
|
||||
CliKeyCombo combo = cli_read_ansi_key_combo(cli);
|
||||
FURI_LOG_T(TAG, "code=0x%02x, mod=0x%x\r\n", combo.key, combo.modifiers);
|
||||
|
||||
if(combo.key == CliKeyTab) {
|
||||
cli_handle_autocomplete(cli);
|
||||
|
||||
} else if(combo.key == CliKeySOH) {
|
||||
furi_delay_ms(33); // We are too fast, Minicom is not ready yet
|
||||
cli_motd();
|
||||
cli_prompt(cli);
|
||||
|
||||
} else if(combo.key == CliKeyETX) {
|
||||
cli_reset(cli);
|
||||
cli_prompt(cli);
|
||||
|
||||
} else if(combo.key == CliKeyEOT) {
|
||||
cli_reset(cli);
|
||||
|
||||
} else if(combo.key == CliKeyUp && combo.modifiers == CliModKeyNo) {
|
||||
// Use previous command if line buffer is empty
|
||||
if(furi_string_size(cli->line) == 0 && furi_string_cmp(cli->line, cli->last_line) != 0) {
|
||||
// Set line buffer and cursor position
|
||||
@@ -315,67 +395,85 @@ static void cli_handle_escape(Cli* cli, char c) {
|
||||
// Show new line to user
|
||||
printf("%s", furi_string_get_cstr(cli->line));
|
||||
}
|
||||
} else if(c == 'B') {
|
||||
} else if(c == 'C') {
|
||||
|
||||
} else if(combo.key == CliKeyDown && combo.modifiers == CliModKeyNo) {
|
||||
// Clear input buffer
|
||||
furi_string_reset(cli->line);
|
||||
cli->cursor_position = 0;
|
||||
printf("\r" CLI_PROMPT "\e[0K");
|
||||
|
||||
} else if(combo.key == CliKeyRight && combo.modifiers == CliModKeyNo) {
|
||||
// Move right
|
||||
if(cli->cursor_position < furi_string_size(cli->line)) {
|
||||
cli->cursor_position++;
|
||||
printf("\e[C");
|
||||
}
|
||||
} else if(c == 'D') {
|
||||
|
||||
} else if(combo.key == CliKeyLeft && combo.modifiers == CliModKeyNo) {
|
||||
// Move left
|
||||
if(cli->cursor_position > 0) {
|
||||
cli->cursor_position--;
|
||||
printf("\e[D");
|
||||
}
|
||||
}
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
void cli_process_input(Cli* cli) {
|
||||
char in_chr = cli_getc(cli);
|
||||
size_t rx_len;
|
||||
} else if(combo.key == CliKeyHome && combo.modifiers == CliModKeyNo) {
|
||||
// Move to beginning of line
|
||||
cli->cursor_position = 0;
|
||||
printf("\e[%uG", CLI_PROMPT_LENGTH + 1); // columns start at 1 \(-_-)/
|
||||
|
||||
} else if(combo.key == CliKeyEnd && combo.modifiers == CliModKeyNo) {
|
||||
// Move to end of line
|
||||
cli->cursor_position = furi_string_size(cli->line);
|
||||
printf("\e[%zuG", CLI_PROMPT_LENGTH + cli->cursor_position + 1);
|
||||
|
||||
if(in_chr == CliSymbolAsciiTab) {
|
||||
cli_handle_autocomplete(cli);
|
||||
} else if(in_chr == CliSymbolAsciiSOH) {
|
||||
furi_delay_ms(33); // We are too fast, Minicom is not ready yet
|
||||
cli_motd();
|
||||
cli_prompt(cli);
|
||||
} else if(in_chr == CliSymbolAsciiETX) {
|
||||
cli_reset(cli);
|
||||
cli_prompt(cli);
|
||||
} else if(in_chr == CliSymbolAsciiEOT) {
|
||||
cli_reset(cli);
|
||||
} else if(in_chr == CliSymbolAsciiEsc) {
|
||||
rx_len = cli_read(cli, (uint8_t*)&in_chr, 1);
|
||||
if((rx_len > 0) && (in_chr == '[')) {
|
||||
cli_read(cli, (uint8_t*)&in_chr, 1);
|
||||
cli_handle_escape(cli, in_chr);
|
||||
} else {
|
||||
cli_putc(cli, CliSymbolAsciiBell);
|
||||
}
|
||||
} else if(in_chr == CliSymbolAsciiBackspace || in_chr == CliSymbolAsciiDel) {
|
||||
cli_handle_backspace(cli);
|
||||
} else if(in_chr == CliSymbolAsciiCR) {
|
||||
cli_handle_enter(cli);
|
||||
} else if(
|
||||
(in_chr >= 0x20 && in_chr < 0x7F) && //-V560
|
||||
combo.modifiers == CliModKeyCtrl &&
|
||||
(combo.key == CliKeyLeft || combo.key == CliKeyRight)) {
|
||||
// Skip run of similar chars to the left or right
|
||||
CliSkipDirection direction = (combo.key == CliKeyLeft) ? CliSkipDirectionLeft :
|
||||
CliSkipDirectionRight;
|
||||
cli->cursor_position = cli_skip_run(cli->line, cli->cursor_position, direction);
|
||||
printf("\e[%zuG", CLI_PROMPT_LENGTH + cli->cursor_position + 1);
|
||||
|
||||
} else if(combo.key == CliKeyBackspace || combo.key == CliKeyDEL) {
|
||||
cli_handle_backspace(cli);
|
||||
|
||||
} else if(combo.key == CliKeyETB) { // Ctrl + Backspace
|
||||
// Delete run of similar chars to the left
|
||||
size_t run_start = cli_skip_run(cli->line, cli->cursor_position, CliSkipDirectionLeft);
|
||||
furi_string_replace_at(cli->line, run_start, cli->cursor_position - run_start, "");
|
||||
cli->cursor_position = run_start;
|
||||
printf(
|
||||
"\e[%zuG%s\e[0K\e[%zuG", // move cursor, print second half of line, erase remains, move cursor again
|
||||
CLI_PROMPT_LENGTH + cli->cursor_position + 1,
|
||||
furi_string_get_cstr(cli->line) + run_start,
|
||||
CLI_PROMPT_LENGTH + run_start + 1);
|
||||
|
||||
} else if(combo.key == CliKeyCR) {
|
||||
cli_handle_enter(cli);
|
||||
|
||||
} else if(
|
||||
(combo.key >= 0x20 && combo.key < 0x7F) && //-V560
|
||||
(furi_string_size(cli->line) < CLI_INPUT_LEN_LIMIT)) {
|
||||
if(cli->cursor_position == furi_string_size(cli->line)) {
|
||||
furi_string_push_back(cli->line, in_chr);
|
||||
cli_putc(cli, in_chr);
|
||||
furi_string_push_back(cli->line, combo.key);
|
||||
cli_putc(cli, combo.key);
|
||||
} else {
|
||||
// Insert character to line buffer
|
||||
const char in_str[2] = {in_chr, 0};
|
||||
const char in_str[2] = {combo.key, 0};
|
||||
furi_string_replace_at(cli->line, cli->cursor_position, 0, in_str);
|
||||
|
||||
// Print character in replace mode
|
||||
printf("\e[4h%c\e[4l", in_chr);
|
||||
printf("\e[4h%c\e[4l", combo.key);
|
||||
fflush(stdout);
|
||||
}
|
||||
cli->cursor_position++;
|
||||
|
||||
} else {
|
||||
cli_putc(cli, CliSymbolAsciiBell);
|
||||
cli_putc(cli, CliKeyBell);
|
||||
}
|
||||
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
void cli_add_command(
|
||||
|
||||
@@ -10,26 +10,12 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
CliSymbolAsciiSOH = 0x01,
|
||||
CliSymbolAsciiETX = 0x03,
|
||||
CliSymbolAsciiEOT = 0x04,
|
||||
CliSymbolAsciiBell = 0x07,
|
||||
CliSymbolAsciiBackspace = 0x08,
|
||||
CliSymbolAsciiTab = 0x09,
|
||||
CliSymbolAsciiLF = 0x0A,
|
||||
CliSymbolAsciiCR = 0x0D,
|
||||
CliSymbolAsciiEsc = 0x1B,
|
||||
CliSymbolAsciiUS = 0x1F,
|
||||
CliSymbolAsciiSpace = 0x20,
|
||||
CliSymbolAsciiDel = 0x7F,
|
||||
} CliSymbols;
|
||||
|
||||
typedef enum {
|
||||
CliCommandFlagDefault = 0, /**< Default, loader lock is used */
|
||||
CliCommandFlagParallelSafe =
|
||||
(1 << 0), /**< Safe to run in parallel with other apps, loader lock is not used */
|
||||
CliCommandFlagInsomniaSafe = (1 << 1), /**< Safe to run with insomnia mode on */
|
||||
CliCommandFlagHidden = (1 << 2), /**< Not shown in `help` */
|
||||
} CliCommandFlag;
|
||||
|
||||
#define RECORD_CLI "cli"
|
||||
|
||||
76
applications/services/cli/cli_ansi.c
Normal file
76
applications/services/cli/cli_ansi.c
Normal file
@@ -0,0 +1,76 @@
|
||||
#include "cli_ansi.h"
|
||||
|
||||
/**
|
||||
* @brief Converts a single character representing a special key into the enum
|
||||
* representation
|
||||
*/
|
||||
static CliKey cli_ansi_key_from_mnemonic(char c) {
|
||||
switch(c) {
|
||||
case 'A':
|
||||
return CliKeyUp;
|
||||
case 'B':
|
||||
return CliKeyDown;
|
||||
case 'C':
|
||||
return CliKeyRight;
|
||||
case 'D':
|
||||
return CliKeyLeft;
|
||||
case 'F':
|
||||
return CliKeyEnd;
|
||||
case 'H':
|
||||
return CliKeyHome;
|
||||
default:
|
||||
return CliKeyUnrecognized;
|
||||
}
|
||||
}
|
||||
|
||||
CliKeyCombo cli_read_ansi_key_combo(Cli* cli) {
|
||||
char ch = cli_getc(cli);
|
||||
|
||||
if(ch != CliKeyEsc)
|
||||
return (CliKeyCombo){
|
||||
.modifiers = CliModKeyNo,
|
||||
.key = ch,
|
||||
};
|
||||
|
||||
ch = cli_getc(cli);
|
||||
|
||||
// ESC ESC -> ESC
|
||||
if(ch == '\e')
|
||||
return (CliKeyCombo){
|
||||
.modifiers = CliModKeyNo,
|
||||
.key = '\e',
|
||||
};
|
||||
|
||||
// ESC <char> -> Alt + <char>
|
||||
if(ch != '[')
|
||||
return (CliKeyCombo){
|
||||
.modifiers = CliModKeyAlt,
|
||||
.key = cli_getc(cli),
|
||||
};
|
||||
|
||||
ch = cli_getc(cli);
|
||||
|
||||
// ESC [ 1
|
||||
if(ch == '1') {
|
||||
// ESC [ 1 ; <modifier bitfield> <key mnemonic>
|
||||
if(cli_getc(cli) == ';') {
|
||||
CliModKey modifiers = (cli_getc(cli) - '0'); // convert following digit to a number
|
||||
modifiers &= ~1;
|
||||
return (CliKeyCombo){
|
||||
.modifiers = modifiers,
|
||||
.key = cli_ansi_key_from_mnemonic(cli_getc(cli)),
|
||||
};
|
||||
}
|
||||
|
||||
return (CliKeyCombo){
|
||||
.modifiers = CliModKeyNo,
|
||||
.key = CliKeyUnrecognized,
|
||||
};
|
||||
}
|
||||
|
||||
// ESC [ <key mnemonic>
|
||||
return (CliKeyCombo){
|
||||
.modifiers = CliModKeyNo,
|
||||
.key = cli_ansi_key_from_mnemonic(ch),
|
||||
};
|
||||
}
|
||||
94
applications/services/cli/cli_ansi.h
Normal file
94
applications/services/cli/cli_ansi.h
Normal file
@@ -0,0 +1,94 @@
|
||||
#pragma once
|
||||
|
||||
#include "cli.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define ANSI_RESET "\e[0m"
|
||||
#define ANSI_BOLD "\e[1m"
|
||||
#define ANSI_FAINT "\e[2m"
|
||||
|
||||
#define ANSI_FG_BLACK "\e[30m"
|
||||
#define ANSI_FG_RED "\e[31m"
|
||||
#define ANSI_FG_GREEN "\e[32m"
|
||||
#define ANSI_FG_YELLOW "\e[33m"
|
||||
#define ANSI_FG_BLUE "\e[34m"
|
||||
#define ANSI_FG_MAGENTA "\e[35m"
|
||||
#define ANSI_FG_CYAN "\e[36m"
|
||||
#define ANSI_FG_WHITE "\e[37m"
|
||||
#define ANSI_FG_BR_BLACK "\e[90m"
|
||||
#define ANSI_FG_BR_RED "\e[91m"
|
||||
#define ANSI_FG_BR_GREEN "\e[92m"
|
||||
#define ANSI_FG_BR_YELLOW "\e[93m"
|
||||
#define ANSI_FG_BR_BLUE "\e[94m"
|
||||
#define ANSI_FG_BR_MAGENTA "\e[95m"
|
||||
#define ANSI_FG_BR_CYAN "\e[96m"
|
||||
#define ANSI_FG_BR_WHITE "\e[97m"
|
||||
|
||||
#define ANSI_BG_BLACK "\e[40m"
|
||||
#define ANSI_BG_RED "\e[41m"
|
||||
#define ANSI_BG_GREEN "\e[42m"
|
||||
#define ANSI_BG_YELLOW "\e[43m"
|
||||
#define ANSI_BG_BLUE "\e[44m"
|
||||
#define ANSI_BG_MAGENTA "\e[45m"
|
||||
#define ANSI_BG_CYAN "\e[46m"
|
||||
#define ANSI_BG_WHITE "\e[47m"
|
||||
#define ANSI_BG_BR_BLACK "\e[100m"
|
||||
#define ANSI_BG_BR_RED "\e[101m"
|
||||
#define ANSI_BG_BR_GREEN "\e[102m"
|
||||
#define ANSI_BG_BR_YELLOW "\e[103m"
|
||||
#define ANSI_BG_BR_BLUE "\e[104m"
|
||||
#define ANSI_BG_BR_MAGENTA "\e[105m"
|
||||
#define ANSI_BG_BR_CYAN "\e[106m"
|
||||
#define ANSI_BG_BR_WHITE "\e[107m"
|
||||
|
||||
#define ANSI_FLIPPER_BRAND_ORANGE "\e[38;2;255;130;0m"
|
||||
|
||||
typedef enum {
|
||||
CliKeyUnrecognized = 0,
|
||||
|
||||
CliKeySOH = 0x01,
|
||||
CliKeyETX = 0x03,
|
||||
CliKeyEOT = 0x04,
|
||||
CliKeyBell = 0x07,
|
||||
CliKeyBackspace = 0x08,
|
||||
CliKeyTab = 0x09,
|
||||
CliKeyLF = 0x0A,
|
||||
CliKeyCR = 0x0D,
|
||||
CliKeyETB = 0x17,
|
||||
CliKeyEsc = 0x1B,
|
||||
CliKeyUS = 0x1F,
|
||||
CliKeySpace = 0x20,
|
||||
CliKeyDEL = 0x7F,
|
||||
|
||||
CliKeySpecial = 0x80,
|
||||
CliKeyLeft,
|
||||
CliKeyRight,
|
||||
CliKeyUp,
|
||||
CliKeyDown,
|
||||
CliKeyHome,
|
||||
CliKeyEnd,
|
||||
} CliKey;
|
||||
|
||||
typedef enum {
|
||||
CliModKeyNo = 0,
|
||||
CliModKeyAlt = 2,
|
||||
CliModKeyCtrl = 4,
|
||||
CliModKeyMeta = 8,
|
||||
} CliModKey;
|
||||
|
||||
typedef struct {
|
||||
CliModKey modifiers;
|
||||
CliKey key;
|
||||
} CliKeyCombo;
|
||||
|
||||
/**
|
||||
* @brief Reads a key or key combination
|
||||
*/
|
||||
CliKeyCombo cli_read_ansi_key_combo(Cli* cli);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "cli_commands.h"
|
||||
#include "cli_command_gpio.h"
|
||||
#include "cli_ansi.h"
|
||||
|
||||
#include <core/thread.h>
|
||||
#include <furi_hal.h>
|
||||
@@ -10,6 +11,7 @@
|
||||
#include <loader/loader.h>
|
||||
#include <lib/toolbox/args.h>
|
||||
#include <lib/toolbox/strint.h>
|
||||
#include <storage/storage.h>
|
||||
|
||||
// Close to ISO, `date +'%Y-%m-%d %H:%M:%S %u'`
|
||||
#define CLI_DATE_FORMAT "%.4d-%.2d-%.2d %.2d:%.2d:%.2d %d"
|
||||
@@ -52,37 +54,196 @@ void cli_command_info(Cli* cli, FuriString* args, void* context) {
|
||||
}
|
||||
}
|
||||
|
||||
void cli_command_help(Cli* cli, FuriString* args, void* context) {
|
||||
// Lil Easter egg :>
|
||||
void cli_command_neofetch(Cli* cli, FuriString* args, void* context) {
|
||||
UNUSED(cli);
|
||||
UNUSED(args);
|
||||
UNUSED(context);
|
||||
|
||||
static const char* const neofetch_logo[] = {
|
||||
" _.-------.._ -,",
|
||||
" .-\"```\"--..,,_/ /`-, -, \\ ",
|
||||
" .:\" /:/ /'\\ \\ ,_..., `. | |",
|
||||
" / ,----/:/ /`\\ _\\~`_-\"` _;",
|
||||
" ' / /`\"\"\"'\\ \\ \\.~`_-' ,-\"'/ ",
|
||||
" | | | 0 | | .-' ,/` /",
|
||||
" | ,..\\ \\ ,.-\"` ,/` /",
|
||||
"; : `/`\"\"\\` ,/--==,/-----,",
|
||||
"| `-...| -.___-Z:_______J...---;",
|
||||
": ` _-'",
|
||||
};
|
||||
#define NEOFETCH_COLOR ANSI_FLIPPER_BRAND_ORANGE
|
||||
|
||||
// Determine logo parameters
|
||||
size_t logo_height = COUNT_OF(neofetch_logo), logo_width = 0;
|
||||
for(size_t i = 0; i < logo_height; i++)
|
||||
logo_width = MAX(logo_width, strlen(neofetch_logo[i]));
|
||||
logo_width += 4; // space between logo and info
|
||||
|
||||
// Format hostname delimiter
|
||||
const size_t size_of_hostname = 4 + strlen(furi_hal_version_get_name_ptr());
|
||||
char delimiter[64];
|
||||
memset(delimiter, '-', size_of_hostname);
|
||||
delimiter[size_of_hostname] = '\0';
|
||||
|
||||
// Get heap info
|
||||
size_t heap_total = memmgr_get_total_heap();
|
||||
size_t heap_used = heap_total - memmgr_get_free_heap();
|
||||
uint16_t heap_percent = (100 * heap_used) / heap_total;
|
||||
|
||||
// Get storage info
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
uint64_t ext_total, ext_free, ext_used, ext_percent;
|
||||
storage_common_fs_info(storage, "/ext", &ext_total, &ext_free);
|
||||
ext_used = ext_total - ext_free;
|
||||
ext_percent = (100 * ext_used) / ext_total;
|
||||
ext_used /= 1024 * 1024;
|
||||
ext_total /= 1024 * 1024;
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
// Get battery info
|
||||
uint16_t charge_percent = furi_hal_power_get_pct();
|
||||
const char* charge_state;
|
||||
if(furi_hal_power_is_charging()) {
|
||||
if((charge_percent < 100) && (!furi_hal_power_is_charging_done())) {
|
||||
charge_state = "charging";
|
||||
} else {
|
||||
charge_state = "charged";
|
||||
}
|
||||
} else {
|
||||
charge_state = "discharging";
|
||||
}
|
||||
|
||||
// Get misc info
|
||||
uint32_t uptime = furi_get_tick() / furi_kernel_get_tick_frequency();
|
||||
const Version* version = version_get();
|
||||
uint16_t major, minor;
|
||||
furi_hal_info_get_api_version(&major, &minor);
|
||||
|
||||
// Print ASCII art with info
|
||||
const size_t info_height = 16;
|
||||
for(size_t i = 0; i < MAX(logo_height, info_height); i++) {
|
||||
printf(NEOFETCH_COLOR "%-*s", logo_width, (i < logo_height) ? neofetch_logo[i] : "");
|
||||
switch(i) {
|
||||
case 0: // you@<hostname>
|
||||
printf("you" ANSI_RESET "@" NEOFETCH_COLOR "%s", furi_hal_version_get_name_ptr());
|
||||
break;
|
||||
case 1: // delimiter
|
||||
printf(ANSI_RESET "%s", delimiter);
|
||||
break;
|
||||
case 2: // OS: FURI <edition> <branch> <version> <commit> (SDK <maj>.<min>)
|
||||
printf(
|
||||
"OS" ANSI_RESET ": FURI %s %s %s %s (SDK %hu.%hu)",
|
||||
version_get_version(version),
|
||||
version_get_gitbranch(version),
|
||||
version_get_version(version),
|
||||
version_get_githash(version),
|
||||
major,
|
||||
minor);
|
||||
break;
|
||||
case 3: // Host: <model> <hostname>
|
||||
printf(
|
||||
"Host" ANSI_RESET ": %s %s",
|
||||
furi_hal_version_get_model_code(),
|
||||
furi_hal_version_get_device_name_ptr());
|
||||
break;
|
||||
case 4: // Kernel: FreeRTOS <maj>.<min>.<build>
|
||||
printf(
|
||||
"Kernel" ANSI_RESET ": FreeRTOS %d.%d.%d",
|
||||
tskKERNEL_VERSION_MAJOR,
|
||||
tskKERNEL_VERSION_MINOR,
|
||||
tskKERNEL_VERSION_BUILD);
|
||||
break;
|
||||
case 5: // Uptime: ?h?m?s
|
||||
printf(
|
||||
"Uptime" ANSI_RESET ": %luh%lum%lus",
|
||||
uptime / 60 / 60,
|
||||
uptime / 60 % 60,
|
||||
uptime % 60);
|
||||
break;
|
||||
case 6: // ST7567 128x64 @ 1 bpp in 1.4"
|
||||
printf("Display" ANSI_RESET ": ST7567 128x64 @ 1 bpp in 1.4\"");
|
||||
break;
|
||||
case 7: // DE: GuiSrv
|
||||
printf("DE" ANSI_RESET ": GuiSrv");
|
||||
break;
|
||||
case 8: // Shell: CliSrv
|
||||
printf("Shell" ANSI_RESET ": CliSrv");
|
||||
break;
|
||||
case 9: // CPU: STM32WB55RG @ 64 MHz
|
||||
printf("CPU" ANSI_RESET ": STM32WB55RG @ 64 MHz");
|
||||
break;
|
||||
case 10: // Memory: <used> / <total> B (??%)
|
||||
printf(
|
||||
"Memory" ANSI_RESET ": %zu / %zu B (%hu%%)", heap_used, heap_total, heap_percent);
|
||||
break;
|
||||
case 11: // Disk (/ext): <used> / <total> MiB (??%)
|
||||
printf(
|
||||
"Disk (/ext)" ANSI_RESET ": %llu / %llu MiB (%llu%%)",
|
||||
ext_used,
|
||||
ext_total,
|
||||
ext_percent);
|
||||
break;
|
||||
case 12: // Battery: ??% (<state>)
|
||||
printf("Battery" ANSI_RESET ": %hu%% (%s)" ANSI_RESET, charge_percent, charge_state);
|
||||
break;
|
||||
case 13: // empty space
|
||||
break;
|
||||
case 14: // Colors (line 1)
|
||||
for(size_t j = 30; j <= 37; j++)
|
||||
printf("\e[%dm███", j);
|
||||
break;
|
||||
case 15: // Colors (line 2)
|
||||
for(size_t j = 90; j <= 97; j++)
|
||||
printf("\e[%dm███", j);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
printf("\r\n");
|
||||
}
|
||||
printf(ANSI_RESET);
|
||||
#undef NEOFETCH_COLOR
|
||||
}
|
||||
|
||||
void cli_command_help(Cli* cli, FuriString* args, void* context) {
|
||||
UNUSED(context);
|
||||
printf("Commands available:");
|
||||
|
||||
// Command count
|
||||
const size_t commands_count = CliCommandTree_size(cli->commands);
|
||||
const size_t commands_count_mid = commands_count / 2 + commands_count % 2;
|
||||
// Count non-hidden commands
|
||||
CliCommandTree_it_t it_count;
|
||||
CliCommandTree_it(it_count, cli->commands);
|
||||
size_t commands_count = 0;
|
||||
while(!CliCommandTree_end_p(it_count)) {
|
||||
if(!(CliCommandTree_cref(it_count)->value_ptr->flags & CliCommandFlagHidden))
|
||||
commands_count++;
|
||||
CliCommandTree_next(it_count);
|
||||
}
|
||||
|
||||
// Use 2 iterators from start and middle to show 2 columns
|
||||
CliCommandTree_it_t it_left;
|
||||
CliCommandTree_it(it_left, cli->commands);
|
||||
CliCommandTree_it_t it_right;
|
||||
CliCommandTree_it(it_right, cli->commands);
|
||||
for(size_t i = 0; i < commands_count_mid; i++)
|
||||
CliCommandTree_next(it_right);
|
||||
// Create iterators starting at different positions
|
||||
const size_t columns = 3;
|
||||
const size_t commands_per_column = (commands_count / columns) + (commands_count % columns);
|
||||
CliCommandTree_it_t iterators[columns];
|
||||
for(size_t c = 0; c < columns; c++) {
|
||||
CliCommandTree_it(iterators[c], cli->commands);
|
||||
for(size_t i = 0; i < c * commands_per_column; i++)
|
||||
CliCommandTree_next(iterators[c]);
|
||||
}
|
||||
|
||||
// Iterate throw tree
|
||||
for(size_t i = 0; i < commands_count_mid; i++) {
|
||||
// Print commands
|
||||
for(size_t r = 0; r < commands_per_column; r++) {
|
||||
printf("\r\n");
|
||||
// Left Column
|
||||
if(!CliCommandTree_end_p(it_left)) {
|
||||
printf("%-30s", furi_string_get_cstr(*CliCommandTree_ref(it_left)->key_ptr));
|
||||
CliCommandTree_next(it_left);
|
||||
|
||||
for(size_t c = 0; c < columns; c++) {
|
||||
if(!CliCommandTree_end_p(iterators[c])) {
|
||||
const CliCommandTree_itref_t* item = CliCommandTree_cref(iterators[c]);
|
||||
if(!(item->value_ptr->flags & CliCommandFlagHidden)) {
|
||||
printf("%-30s", furi_string_get_cstr(*item->key_ptr));
|
||||
}
|
||||
CliCommandTree_next(iterators[c]);
|
||||
}
|
||||
}
|
||||
// Right Column
|
||||
if(!CliCommandTree_end_p(it_right)) {
|
||||
printf("%s", furi_string_get_cstr(*CliCommandTree_ref(it_right)->key_ptr));
|
||||
CliCommandTree_next(it_right);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if(furi_string_size(args) > 0) {
|
||||
cli_nl(cli);
|
||||
@@ -391,16 +552,18 @@ static void cli_command_top(Cli* cli, FuriString* args, void* context) {
|
||||
int interval = 1000;
|
||||
args_read_int_and_trim(args, &interval);
|
||||
|
||||
if(interval) printf("\e[2J\e[?25l"); // Clear display, hide cursor
|
||||
|
||||
FuriThreadList* thread_list = furi_thread_list_alloc();
|
||||
while(!cli_cmd_interrupt_received(cli)) {
|
||||
uint32_t tick = furi_get_tick();
|
||||
furi_thread_enumerate(thread_list);
|
||||
|
||||
if(interval) printf("\e[2J\e[0;0f"); // Clear display and return to 0
|
||||
if(interval) printf("\e[0;0f"); // Return to 0,0
|
||||
|
||||
uint32_t uptime = tick / furi_kernel_get_tick_frequency();
|
||||
printf(
|
||||
"Threads: %zu, ISR Time: %0.2f%%, Uptime: %luh%lum%lus\r\n",
|
||||
"\rThreads: %zu, ISR Time: %0.2f%%, Uptime: %luh%lum%lus\e[0K\r\n",
|
||||
furi_thread_list_size(thread_list),
|
||||
(double)furi_thread_list_get_isr_time(thread_list),
|
||||
uptime / 60 / 60,
|
||||
@@ -408,14 +571,14 @@ static void cli_command_top(Cli* cli, FuriString* args, void* context) {
|
||||
uptime % 60);
|
||||
|
||||
printf(
|
||||
"Heap: total %zu, free %zu, minimum %zu, max block %zu\r\n\r\n",
|
||||
"\rHeap: total %zu, free %zu, minimum %zu, max block %zu\e[0K\r\n\r\n",
|
||||
memmgr_get_total_heap(),
|
||||
memmgr_get_free_heap(),
|
||||
memmgr_get_minimum_free_heap(),
|
||||
memmgr_heap_get_max_free_block());
|
||||
|
||||
printf(
|
||||
"%-17s %-20s %-10s %5s %12s %6s %10s %7s %5s\r\n",
|
||||
"\r%-17s %-20s %-10s %5s %12s %6s %10s %7s %5s\e[0K\r\n",
|
||||
"AppID",
|
||||
"Name",
|
||||
"State",
|
||||
@@ -429,7 +592,7 @@ static void cli_command_top(Cli* cli, FuriString* args, void* context) {
|
||||
for(size_t i = 0; i < furi_thread_list_size(thread_list); i++) {
|
||||
const FuriThreadListItem* item = furi_thread_list_get_at(thread_list, i);
|
||||
printf(
|
||||
"%-17s %-20s %-10s %5d 0x%08lx %6lu %10lu %7zu %5.1f\r\n",
|
||||
"\r%-17s %-20s %-10s %5d 0x%08lx %6lu %10lu %7zu %5.1f\e[0K\r\n",
|
||||
item->app_id,
|
||||
item->name,
|
||||
item->state,
|
||||
@@ -448,6 +611,8 @@ static void cli_command_top(Cli* cli, FuriString* args, void* context) {
|
||||
}
|
||||
}
|
||||
furi_thread_list_free(thread_list);
|
||||
|
||||
if(interval) printf("\e[?25h"); // Show cursor
|
||||
}
|
||||
|
||||
void cli_command_free(Cli* cli, FuriString* args, void* context) {
|
||||
@@ -499,6 +664,12 @@ void cli_commands_init(Cli* cli) {
|
||||
cli_add_command(cli, "!", CliCommandFlagParallelSafe, cli_command_info, (void*)true);
|
||||
cli_add_command(cli, "info", CliCommandFlagParallelSafe, cli_command_info, NULL);
|
||||
cli_add_command(cli, "device_info", CliCommandFlagParallelSafe, cli_command_info, (void*)true);
|
||||
cli_add_command(
|
||||
cli,
|
||||
"neofetch",
|
||||
CliCommandFlagParallelSafe | CliCommandFlagHidden,
|
||||
cli_command_neofetch,
|
||||
NULL);
|
||||
|
||||
cli_add_command(cli, "?", CliCommandFlagParallelSafe, cli_command_help, NULL);
|
||||
cli_add_command(cli, "help", CliCommandFlagParallelSafe, cli_command_help, NULL);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <lib/toolbox/args.h>
|
||||
#include <cli/cli.h>
|
||||
#include <cli/cli_ansi.h>
|
||||
|
||||
void crypto_cli_print_usage(void) {
|
||||
printf("Usage:\r\n");
|
||||
@@ -45,14 +46,14 @@ void crypto_cli_encrypt(Cli* cli, FuriString* args) {
|
||||
input = furi_string_alloc();
|
||||
char c;
|
||||
while(cli_read(cli, (uint8_t*)&c, 1) == 1) {
|
||||
if(c == CliSymbolAsciiETX) {
|
||||
if(c == CliKeyETX) {
|
||||
printf("\r\n");
|
||||
break;
|
||||
} else if(c >= 0x20 && c < 0x7F) {
|
||||
putc(c, stdout);
|
||||
fflush(stdout);
|
||||
furi_string_push_back(input, c);
|
||||
} else if(c == CliSymbolAsciiCR) {
|
||||
} else if(c == CliKeyCR) {
|
||||
printf("\r\n");
|
||||
furi_string_cat(input, "\r\n");
|
||||
}
|
||||
@@ -120,14 +121,14 @@ void crypto_cli_decrypt(Cli* cli, FuriString* args) {
|
||||
hex_input = furi_string_alloc();
|
||||
char c;
|
||||
while(cli_read(cli, (uint8_t*)&c, 1) == 1) {
|
||||
if(c == CliSymbolAsciiETX) {
|
||||
if(c == CliKeyETX) {
|
||||
printf("\r\n");
|
||||
break;
|
||||
} else if(c >= 0x20 && c < 0x7F) {
|
||||
putc(c, stdout);
|
||||
fflush(stdout);
|
||||
furi_string_push_back(hex_input, c);
|
||||
} else if(c == CliSymbolAsciiCR) {
|
||||
} else if(c == CliKeyCR) {
|
||||
printf("\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,11 +54,14 @@ bool dialogs_app_process_module_file_browser(const DialogsAppMessageDataFileBrow
|
||||
ret = file_browser_context->result;
|
||||
|
||||
view_holder_set_view(view_holder, NULL);
|
||||
view_holder_free(view_holder);
|
||||
file_browser_stop(file_browser);
|
||||
|
||||
file_browser_free(file_browser);
|
||||
view_holder_free(view_holder);
|
||||
|
||||
api_lock_free(file_browser_context->lock);
|
||||
free(file_browser_context);
|
||||
|
||||
furi_record_close(RECORD_GUI);
|
||||
|
||||
return ret;
|
||||
|
||||
@@ -308,12 +308,14 @@ static void loader_applications_closed_callback(void* context) {
|
||||
furi_message_queue_put(loader->queue, &message, FuriWaitForever);
|
||||
}
|
||||
|
||||
static void loader_thread_state_callback(FuriThreadState thread_state, void* context) {
|
||||
static void
|
||||
loader_thread_state_callback(FuriThread* thread, FuriThreadState thread_state, void* context) {
|
||||
UNUSED(thread);
|
||||
furi_assert(context);
|
||||
|
||||
Loader* loader = context;
|
||||
|
||||
if(thread_state == FuriThreadStateStopped) {
|
||||
Loader* loader = context;
|
||||
|
||||
LoaderMessage message;
|
||||
message.type = LoaderMessageTypeAppClosed;
|
||||
furi_message_queue_put(loader->queue, &message, FuriWaitForever);
|
||||
|
||||
@@ -104,19 +104,12 @@ static int32_t region_load_file(void* context) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void region_loader_pending_callback(void* context, uint32_t arg) {
|
||||
UNUSED(arg);
|
||||
|
||||
FuriThread* loader = context;
|
||||
furi_thread_join(loader);
|
||||
furi_thread_free(loader);
|
||||
}
|
||||
|
||||
static void region_loader_state_callback(FuriThreadState state, void* context) {
|
||||
static void
|
||||
region_loader_release_callback(FuriThread* thread, FuriThreadState state, void* context) {
|
||||
UNUSED(context);
|
||||
|
||||
if(state == FuriThreadStateStopped) {
|
||||
furi_timer_pending_callback(region_loader_pending_callback, furi_thread_get_current(), 0);
|
||||
furi_thread_free(thread);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,7 +119,7 @@ static void region_storage_callback(const void* message, void* context) {
|
||||
|
||||
if(event->type == StorageEventTypeCardMount) {
|
||||
FuriThread* loader = furi_thread_alloc_ex(NULL, 2048, region_load_file, NULL);
|
||||
furi_thread_set_state_callback(loader, region_loader_state_callback);
|
||||
furi_thread_set_state_callback(loader, region_loader_release_callback);
|
||||
furi_thread_start(loader);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -373,8 +373,10 @@ static void rpc_session_thread_pending_callback(void* context, uint32_t arg) {
|
||||
free(session);
|
||||
}
|
||||
|
||||
static void rpc_session_thread_state_callback(FuriThreadState thread_state, void* context) {
|
||||
if(thread_state == FuriThreadStateStopped) {
|
||||
static void
|
||||
rpc_session_thread_state_callback(FuriThread* thread, FuriThreadState state, void* context) {
|
||||
UNUSED(thread);
|
||||
if(state == FuriThreadStateStopped) {
|
||||
furi_timer_pending_callback(rpc_session_thread_pending_callback, context, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <furi_hal.h>
|
||||
|
||||
#include <cli/cli.h>
|
||||
#include <cli/cli_ansi.h>
|
||||
#include <lib/toolbox/args.h>
|
||||
#include <lib/toolbox/dir_walk.h>
|
||||
#include <lib/toolbox/md5_calc.h>
|
||||
@@ -224,7 +225,7 @@ static void storage_cli_write(Cli* cli, FuriString* path, FuriString* args) {
|
||||
while(true) {
|
||||
uint8_t symbol = cli_getc(cli);
|
||||
|
||||
if(symbol == CliSymbolAsciiETX) {
|
||||
if(symbol == CliKeyETX) {
|
||||
size_t write_size = read_index % buffer_size;
|
||||
|
||||
if(write_size > 0) {
|
||||
|
||||
12
applications/system/bad_ble/application.fam
Normal file
12
applications/system/bad_ble/application.fam
Normal file
@@ -0,0 +1,12 @@
|
||||
App(
|
||||
appid="bad_ble",
|
||||
name="Bad BLE",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="bad_ble_app",
|
||||
stack_size=2 * 1024,
|
||||
icon="A_BadUsb_14",
|
||||
fap_libs=["assets", "ble_profile"],
|
||||
fap_icon="icon.png",
|
||||
fap_icon_assets="assets",
|
||||
fap_category="Bluetooth",
|
||||
)
|
||||
BIN
applications/system/bad_ble/assets/Bad_BLE_48x22.png
Normal file
BIN
applications/system/bad_ble/assets/Bad_BLE_48x22.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 145 B |
196
applications/system/bad_ble/bad_ble_app.c
Normal file
196
applications/system/bad_ble/bad_ble_app.c
Normal file
@@ -0,0 +1,196 @@
|
||||
#include "bad_ble_app_i.h"
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <storage/storage.h>
|
||||
#include <lib/toolbox/path.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
|
||||
#define BAD_BLE_SETTINGS_PATH BAD_BLE_APP_BASE_FOLDER "/.badble.settings"
|
||||
#define BAD_BLE_SETTINGS_FILE_TYPE "Flipper BadBLE Settings File"
|
||||
#define BAD_BLE_SETTINGS_VERSION 1
|
||||
#define BAD_BLE_SETTINGS_DEFAULT_LAYOUT BAD_BLE_APP_PATH_LAYOUT_FOLDER "/en-US.kl"
|
||||
|
||||
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) {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
FlipperFormat* fff = flipper_format_file_alloc(storage);
|
||||
bool state = false;
|
||||
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
uint32_t version = 0;
|
||||
|
||||
if(flipper_format_file_open_existing(fff, BAD_BLE_SETTINGS_PATH)) {
|
||||
do {
|
||||
if(!flipper_format_read_header(fff, temp_str, &version)) break;
|
||||
if((strcmp(furi_string_get_cstr(temp_str), BAD_BLE_SETTINGS_FILE_TYPE) != 0) ||
|
||||
(version != BAD_BLE_SETTINGS_VERSION))
|
||||
break;
|
||||
|
||||
if(!flipper_format_read_string(fff, "layout", temp_str)) break;
|
||||
|
||||
state = true;
|
||||
} while(0);
|
||||
}
|
||||
flipper_format_free(fff);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
if(state) {
|
||||
furi_string_set(app->keyboard_layout, temp_str);
|
||||
|
||||
Storage* fs_api = furi_record_open(RECORD_STORAGE);
|
||||
FileInfo layout_file_info;
|
||||
FS_Error file_check_err = storage_common_stat(
|
||||
fs_api, furi_string_get_cstr(app->keyboard_layout), &layout_file_info);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
if((file_check_err != FSE_OK) || (layout_file_info.size != 256)) {
|
||||
furi_string_set(app->keyboard_layout, BAD_BLE_SETTINGS_DEFAULT_LAYOUT);
|
||||
}
|
||||
} else {
|
||||
furi_string_set(app->keyboard_layout, BAD_BLE_SETTINGS_DEFAULT_LAYOUT);
|
||||
}
|
||||
|
||||
furi_string_free(temp_str);
|
||||
}
|
||||
|
||||
static void bad_ble_save_settings(BadBleApp* app) {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
FlipperFormat* fff = flipper_format_file_alloc(storage);
|
||||
|
||||
if(flipper_format_file_open_always(fff, BAD_BLE_SETTINGS_PATH)) {
|
||||
do {
|
||||
if(!flipper_format_write_header_cstr(
|
||||
fff, BAD_BLE_SETTINGS_FILE_TYPE, BAD_BLE_SETTINGS_VERSION))
|
||||
break;
|
||||
if(!flipper_format_write_string(fff, "layout", app->keyboard_layout)) break;
|
||||
} while(0);
|
||||
}
|
||||
|
||||
flipper_format_free(fff);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
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();
|
||||
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, BadBleAppViewWidget, widget_get_view(app->widget));
|
||||
|
||||
// Popup
|
||||
app->popup = popup_alloc();
|
||||
view_dispatcher_add_view(app->view_dispatcher, BadBleAppViewPopup, popup_get_view(app->popup));
|
||||
|
||||
app->var_item_list = variable_item_list_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher,
|
||||
BadBleAppViewConfig,
|
||||
variable_item_list_get_view(app->var_item_list));
|
||||
|
||||
app->bad_ble_view = bad_ble_view_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, BadBleAppViewWork, bad_ble_view_get_view(app->bad_ble_view));
|
||||
|
||||
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||
|
||||
if(!furi_string_empty(app->file_path)) {
|
||||
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_view_free(app->bad_ble_view);
|
||||
|
||||
// Custom Widget
|
||||
view_dispatcher_remove_view(app->view_dispatcher, BadBleAppViewWidget);
|
||||
widget_free(app->widget);
|
||||
|
||||
// Popup
|
||||
view_dispatcher_remove_view(app->view_dispatcher, BadBleAppViewPopup);
|
||||
popup_free(app->popup);
|
||||
|
||||
// Config menu
|
||||
view_dispatcher_remove_view(app->view_dispatcher, BadBleAppViewConfig);
|
||||
variable_item_list_free(app->var_item_list);
|
||||
|
||||
// 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);
|
||||
|
||||
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/system/bad_ble/bad_ble_app.h
Normal file
11
applications/system/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
|
||||
53
applications/system/bad_ble/bad_ble_app_i.h
Normal file
53
applications/system/bad_ble/bad_ble_app_i.h
Normal file
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
|
||||
#include "bad_ble_app.h"
|
||||
#include "scenes/bad_ble_scene.h"
|
||||
#include "helpers/ducky_script.h"
|
||||
#include "helpers/bad_ble_hid.h"
|
||||
|
||||
#include <gui/gui.h>
|
||||
#include <assets_icons.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <gui/scene_manager.h>
|
||||
#include <dialogs/dialogs.h>
|
||||
#include <notification/notification_messages.h>
|
||||
#include <gui/modules/variable_item_list.h>
|
||||
#include <gui/modules/widget.h>
|
||||
#include <gui/modules/popup.h>
|
||||
#include "views/bad_ble_view.h"
|
||||
|
||||
#define BAD_BLE_APP_BASE_FOLDER EXT_PATH("badusb")
|
||||
#define BAD_BLE_APP_PATH_LAYOUT_FOLDER BAD_BLE_APP_BASE_FOLDER "/assets/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;
|
||||
Popup* popup;
|
||||
VariableItemList* var_item_list;
|
||||
|
||||
BadBleAppError error;
|
||||
FuriString* file_path;
|
||||
FuriString* keyboard_layout;
|
||||
BadBle* bad_ble_view;
|
||||
BadBleScript* bad_ble_script;
|
||||
|
||||
BadBleHidInterface interface;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
BadBleAppViewWidget,
|
||||
BadBleAppViewPopup,
|
||||
BadBleAppViewWork,
|
||||
BadBleAppViewConfig,
|
||||
} BadBleAppView;
|
||||
157
applications/system/bad_ble/helpers/bad_ble_hid.c
Normal file
157
applications/system/bad_ble/helpers/bad_ble_hid.c
Normal file
@@ -0,0 +1,157 @@
|
||||
#include "bad_ble_hid.h"
|
||||
#include <extra_profiles/hid_profile.h>
|
||||
#include <bt/bt_service/bt.h>
|
||||
#include <storage/storage.h>
|
||||
|
||||
#define TAG "BadBLE HID"
|
||||
|
||||
#define HID_BT_KEYS_STORAGE_NAME ".bt_hid.keys"
|
||||
|
||||
typedef struct {
|
||||
Bt* bt;
|
||||
FuriHalBleProfileBase* profile;
|
||||
HidStateCallback state_callback;
|
||||
void* callback_context;
|
||||
bool is_connected;
|
||||
} BleHidInstance;
|
||||
|
||||
static const BleProfileHidParams ble_hid_params = {
|
||||
.device_name_prefix = "BadBLE",
|
||||
.mac_xor = 0x0002,
|
||||
};
|
||||
|
||||
static void hid_ble_connection_status_callback(BtStatus status, void* context) {
|
||||
furi_assert(context);
|
||||
BleHidInstance* ble_hid = context;
|
||||
ble_hid->is_connected = (status == BtStatusConnected);
|
||||
if(ble_hid->state_callback) {
|
||||
ble_hid->state_callback(ble_hid->is_connected, ble_hid->callback_context);
|
||||
}
|
||||
}
|
||||
|
||||
void* hid_ble_init(FuriHalUsbHidConfig* hid_cfg) {
|
||||
UNUSED(hid_cfg);
|
||||
BleHidInstance* ble_hid = malloc(sizeof(BleHidInstance));
|
||||
ble_hid->bt = furi_record_open(RECORD_BT);
|
||||
bt_disconnect(ble_hid->bt);
|
||||
|
||||
// Wait 2nd core to update nvm storage
|
||||
furi_delay_ms(200);
|
||||
|
||||
bt_keys_storage_set_storage_path(ble_hid->bt, APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME));
|
||||
|
||||
ble_hid->profile = bt_profile_start(ble_hid->bt, ble_profile_hid, (void*)&ble_hid_params);
|
||||
furi_check(ble_hid->profile);
|
||||
|
||||
furi_hal_bt_start_advertising();
|
||||
|
||||
bt_set_status_changed_callback(ble_hid->bt, hid_ble_connection_status_callback, ble_hid);
|
||||
|
||||
return ble_hid;
|
||||
}
|
||||
|
||||
void hid_ble_deinit(void* inst) {
|
||||
BleHidInstance* ble_hid = inst;
|
||||
furi_assert(ble_hid);
|
||||
|
||||
bt_set_status_changed_callback(ble_hid->bt, NULL, NULL);
|
||||
bt_disconnect(ble_hid->bt);
|
||||
|
||||
// Wait 2nd core to update nvm storage
|
||||
furi_delay_ms(200);
|
||||
bt_keys_storage_set_default_path(ble_hid->bt);
|
||||
|
||||
furi_check(bt_profile_restore_default(ble_hid->bt));
|
||||
furi_record_close(RECORD_BT);
|
||||
free(ble_hid);
|
||||
}
|
||||
|
||||
void hid_ble_set_state_callback(void* inst, HidStateCallback cb, void* context) {
|
||||
BleHidInstance* ble_hid = inst;
|
||||
furi_assert(ble_hid);
|
||||
ble_hid->state_callback = cb;
|
||||
ble_hid->callback_context = context;
|
||||
}
|
||||
|
||||
bool hid_ble_is_connected(void* inst) {
|
||||
BleHidInstance* ble_hid = inst;
|
||||
furi_assert(ble_hid);
|
||||
return ble_hid->is_connected;
|
||||
}
|
||||
|
||||
bool hid_ble_kb_press(void* inst, uint16_t button) {
|
||||
BleHidInstance* ble_hid = inst;
|
||||
furi_assert(ble_hid);
|
||||
return ble_profile_hid_kb_press(ble_hid->profile, button);
|
||||
}
|
||||
|
||||
bool hid_ble_kb_release(void* inst, uint16_t button) {
|
||||
BleHidInstance* ble_hid = inst;
|
||||
furi_assert(ble_hid);
|
||||
return ble_profile_hid_kb_release(ble_hid->profile, button);
|
||||
}
|
||||
|
||||
bool hid_ble_consumer_press(void* inst, uint16_t button) {
|
||||
BleHidInstance* ble_hid = inst;
|
||||
furi_assert(ble_hid);
|
||||
return ble_profile_hid_consumer_key_press(ble_hid->profile, button);
|
||||
}
|
||||
|
||||
bool hid_ble_consumer_release(void* inst, uint16_t button) {
|
||||
BleHidInstance* ble_hid = inst;
|
||||
furi_assert(ble_hid);
|
||||
return ble_profile_hid_consumer_key_release(ble_hid->profile, button);
|
||||
}
|
||||
|
||||
bool hid_ble_release_all(void* inst) {
|
||||
BleHidInstance* ble_hid = inst;
|
||||
furi_assert(ble_hid);
|
||||
bool state = ble_profile_hid_kb_release_all(ble_hid->profile);
|
||||
state &= ble_profile_hid_consumer_key_release_all(ble_hid->profile);
|
||||
return state;
|
||||
}
|
||||
|
||||
uint8_t hid_ble_get_led_state(void* inst) {
|
||||
UNUSED(inst);
|
||||
FURI_LOG_W(TAG, "hid_ble_get_led_state not implemented");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const BadBleHidApi hid_api_ble = {
|
||||
.init = hid_ble_init,
|
||||
.deinit = hid_ble_deinit,
|
||||
.set_state_callback = hid_ble_set_state_callback,
|
||||
.is_connected = hid_ble_is_connected,
|
||||
|
||||
.kb_press = hid_ble_kb_press,
|
||||
.kb_release = hid_ble_kb_release,
|
||||
.consumer_press = hid_ble_consumer_press,
|
||||
.consumer_release = hid_ble_consumer_release,
|
||||
.release_all = hid_ble_release_all,
|
||||
.get_led_state = hid_ble_get_led_state,
|
||||
};
|
||||
|
||||
const BadBleHidApi* bad_ble_hid_get_interface(BadBleHidInterface interface) {
|
||||
UNUSED(interface);
|
||||
return &hid_api_ble;
|
||||
}
|
||||
|
||||
void bad_ble_hid_ble_remove_pairing(void) {
|
||||
Bt* bt = furi_record_open(RECORD_BT);
|
||||
bt_disconnect(bt);
|
||||
|
||||
// Wait 2nd core to update nvm storage
|
||||
furi_delay_ms(200);
|
||||
|
||||
furi_hal_bt_stop_advertising();
|
||||
|
||||
bt_keys_storage_set_storage_path(bt, APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME));
|
||||
bt_forget_bonded_devices(bt);
|
||||
|
||||
// Wait 2nd core to update nvm storage
|
||||
furi_delay_ms(200);
|
||||
bt_keys_storage_set_default_path(bt);
|
||||
|
||||
furi_check(bt_profile_restore_default(bt));
|
||||
furi_record_close(RECORD_BT);
|
||||
}
|
||||
34
applications/system/bad_ble/helpers/bad_ble_hid.h
Normal file
34
applications/system/bad_ble/helpers/bad_ble_hid.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
|
||||
typedef enum {
|
||||
BadBleHidInterfaceBle,
|
||||
} BadBleHidInterface;
|
||||
|
||||
typedef struct {
|
||||
void* (*init)(FuriHalUsbHidConfig* hid_cfg);
|
||||
void (*deinit)(void* inst);
|
||||
void (*set_state_callback)(void* inst, HidStateCallback cb, void* context);
|
||||
bool (*is_connected)(void* inst);
|
||||
|
||||
bool (*kb_press)(void* inst, uint16_t button);
|
||||
bool (*kb_release)(void* inst, uint16_t button);
|
||||
bool (*consumer_press)(void* inst, uint16_t button);
|
||||
bool (*consumer_release)(void* inst, uint16_t button);
|
||||
bool (*release_all)(void* inst);
|
||||
uint8_t (*get_led_state)(void* inst);
|
||||
} BadBleHidApi;
|
||||
|
||||
const BadBleHidApi* bad_ble_hid_get_interface(BadBleHidInterface interface);
|
||||
|
||||
void bad_ble_hid_ble_remove_pairing(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
716
applications/system/bad_ble/helpers/ducky_script.c
Normal file
716
applications/system/bad_ble/helpers/ducky_script.c
Normal file
@@ -0,0 +1,716 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <gui/gui.h>
|
||||
#include <input/input.h>
|
||||
#include <lib/toolbox/args.h>
|
||||
#include <lib/toolbox/strint.h>
|
||||
#include <storage/storage.h>
|
||||
#include "ducky_script.h"
|
||||
#include "ducky_script_i.h"
|
||||
#include <dolphin/dolphin.h>
|
||||
|
||||
#define TAG "BadBle"
|
||||
|
||||
#define WORKER_TAG TAG "Worker"
|
||||
|
||||
#define BADUSB_ASCII_TO_KEY(script, x) \
|
||||
(((uint8_t)x < 128) ? (script->layout[(uint8_t)x]) : HID_KEYBOARD_NONE)
|
||||
|
||||
typedef enum {
|
||||
WorkerEvtStartStop = (1 << 0),
|
||||
WorkerEvtPauseResume = (1 << 1),
|
||||
WorkerEvtEnd = (1 << 2),
|
||||
WorkerEvtConnect = (1 << 3),
|
||||
WorkerEvtDisconnect = (1 << 4),
|
||||
} WorkerEvtFlags;
|
||||
|
||||
static const char ducky_cmd_id[] = {"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(BadBleScript* bad_ble, 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 BADUSB_ASCII_TO_KEY(bad_ble, param[0]) & 0xFF;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool ducky_get_number(const char* param, uint32_t* val) {
|
||||
uint32_t value = 0;
|
||||
if(strint_to_uint32(param, NULL, &value, 10) == StrintParseNoError) {
|
||||
*val = value;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ducky_numlock_on(BadBleScript* bad_ble) {
|
||||
if((bad_ble->hid->get_led_state(bad_ble->hid_inst) & HID_KB_LED_NUM) == 0) {
|
||||
bad_ble->hid->kb_press(bad_ble->hid_inst, HID_KEYBOARD_LOCK_NUM_LOCK);
|
||||
bad_ble->hid->kb_release(bad_ble->hid_inst, HID_KEYBOARD_LOCK_NUM_LOCK);
|
||||
}
|
||||
}
|
||||
|
||||
bool ducky_numpad_press(BadBleScript* bad_ble, const char num) {
|
||||
if((num < '0') || (num > '9')) return false;
|
||||
|
||||
uint16_t key = numpad_keys[num - '0'];
|
||||
bad_ble->hid->kb_press(bad_ble->hid_inst, key);
|
||||
bad_ble->hid->kb_release(bad_ble->hid_inst, key);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ducky_altchar(BadBleScript* bad_ble, const char* charcode) {
|
||||
uint8_t i = 0;
|
||||
bool state = false;
|
||||
|
||||
bad_ble->hid->kb_press(bad_ble->hid_inst, KEY_MOD_LEFT_ALT);
|
||||
|
||||
while(!ducky_is_line_end(charcode[i])) {
|
||||
state = ducky_numpad_press(bad_ble, charcode[i]);
|
||||
if(state == false) break;
|
||||
i++;
|
||||
}
|
||||
|
||||
bad_ble->hid->kb_release(bad_ble->hid_inst, KEY_MOD_LEFT_ALT);
|
||||
return state;
|
||||
}
|
||||
|
||||
bool ducky_altstring(BadBleScript* bad_ble, 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_ble, temp_str);
|
||||
if(state == false) break;
|
||||
i++;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
int32_t ducky_error(BadBleScript* bad_ble, const char* text, ...) {
|
||||
va_list args;
|
||||
va_start(args, text);
|
||||
|
||||
vsnprintf(bad_ble->st.error, sizeof(bad_ble->st.error), text, args);
|
||||
|
||||
va_end(args);
|
||||
return SCRIPT_STATE_ERROR;
|
||||
}
|
||||
|
||||
bool ducky_string(BadBleScript* bad_ble, const char* param) {
|
||||
uint32_t i = 0;
|
||||
|
||||
while(param[i] != '\0') {
|
||||
if(param[i] != '\n') {
|
||||
uint16_t keycode = BADUSB_ASCII_TO_KEY(bad_ble, param[i]);
|
||||
if(keycode != HID_KEYBOARD_NONE) {
|
||||
bad_ble->hid->kb_press(bad_ble->hid_inst, keycode);
|
||||
bad_ble->hid->kb_release(bad_ble->hid_inst, keycode);
|
||||
}
|
||||
} else {
|
||||
bad_ble->hid->kb_press(bad_ble->hid_inst, HID_KEYBOARD_RETURN);
|
||||
bad_ble->hid->kb_release(bad_ble->hid_inst, HID_KEYBOARD_RETURN);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
bad_ble->stringdelay = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ducky_string_next(BadBleScript* bad_ble) {
|
||||
if(bad_ble->string_print_pos >= furi_string_size(bad_ble->string_print)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
char print_char = furi_string_get_char(bad_ble->string_print, bad_ble->string_print_pos);
|
||||
|
||||
if(print_char != '\n') {
|
||||
uint16_t keycode = BADUSB_ASCII_TO_KEY(bad_ble, print_char);
|
||||
if(keycode != HID_KEYBOARD_NONE) {
|
||||
bad_ble->hid->kb_press(bad_ble->hid_inst, keycode);
|
||||
bad_ble->hid->kb_release(bad_ble->hid_inst, keycode);
|
||||
}
|
||||
} else {
|
||||
bad_ble->hid->kb_press(bad_ble->hid_inst, HID_KEYBOARD_RETURN);
|
||||
bad_ble->hid->kb_release(bad_ble->hid_inst, HID_KEYBOARD_RETURN);
|
||||
}
|
||||
|
||||
bad_ble->string_print_pos++;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static int32_t ducky_parse_line(BadBleScript* bad_ble, 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_ble, line_tmp);
|
||||
if(cmd_result != SCRIPT_STATE_CMD_UNKNOWN) {
|
||||
return cmd_result;
|
||||
}
|
||||
|
||||
// Special keys + modifiers
|
||||
uint16_t key = ducky_get_keycode(bad_ble, line_tmp, false);
|
||||
if(key == HID_KEYBOARD_NONE) {
|
||||
return ducky_error(bad_ble, "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_ble, line_tmp, true);
|
||||
}
|
||||
bad_ble->hid->kb_press(bad_ble->hid_inst, key);
|
||||
bad_ble->hid->kb_release(bad_ble->hid_inst, key);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool ducky_set_usb_id(BadBleScript* bad_ble, const char* line) {
|
||||
if(sscanf(line, "%lX:%lX", &bad_ble->hid_cfg.vid, &bad_ble->hid_cfg.pid) == 2) {
|
||||
bad_ble->hid_cfg.manuf[0] = '\0';
|
||||
bad_ble->hid_cfg.product[0] = '\0';
|
||||
|
||||
uint8_t id_len = ducky_get_command_len(line);
|
||||
if(!ducky_is_line_end(line[id_len + 1])) {
|
||||
sscanf(
|
||||
&line[id_len + 1],
|
||||
"%31[^\r\n:]:%31[^\r\n]",
|
||||
bad_ble->hid_cfg.manuf,
|
||||
bad_ble->hid_cfg.product);
|
||||
}
|
||||
FURI_LOG_D(
|
||||
WORKER_TAG,
|
||||
"set id: %04lX:%04lX mfr:%s product:%s",
|
||||
bad_ble->hid_cfg.vid,
|
||||
bad_ble->hid_cfg.pid,
|
||||
bad_ble->hid_cfg.manuf,
|
||||
bad_ble->hid_cfg.product);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void bad_ble_hid_state_callback(bool state, void* context) {
|
||||
furi_assert(context);
|
||||
BadBleScript* bad_ble = context;
|
||||
|
||||
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 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);
|
||||
|
||||
const char* line_tmp = furi_string_get_cstr(bad_ble->line);
|
||||
bool id_set = false; // Looking for ID command at first line
|
||||
if(strncmp(line_tmp, ducky_cmd_id, strlen(ducky_cmd_id)) == 0) {
|
||||
id_set = ducky_set_usb_id(bad_ble, &line_tmp[strlen(ducky_cmd_id) + 1]);
|
||||
}
|
||||
|
||||
if(id_set) {
|
||||
bad_ble->hid_inst = bad_ble->hid->init(&bad_ble->hid_cfg);
|
||||
} else {
|
||||
bad_ble->hid_inst = bad_ble->hid->init(NULL);
|
||||
}
|
||||
bad_ble->hid->set_state_callback(bad_ble->hid_inst, bad_ble_hid_state_callback, bad_ble);
|
||||
|
||||
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);
|
||||
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_ble->st.error_line = bad_ble->st.line_cur - 1;
|
||||
FURI_LOG_E(WORKER_TAG, "Unknown command at line %zu", 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);
|
||||
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_ble->st.error_line = bad_ble->st.line_cur;
|
||||
FURI_LOG_E(WORKER_TAG, "Unknown command at line %zu", 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 uint32_t bad_ble_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_ble_worker(void* context) {
|
||||
BadBleScript* bad_ble = context;
|
||||
|
||||
BadBleWorkerState worker_state = BadBleStateInit;
|
||||
BadBleWorkerState pause_state = BadBleStateRunning;
|
||||
int32_t delay_val = 0;
|
||||
|
||||
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();
|
||||
bad_ble->string_print = furi_string_alloc();
|
||||
|
||||
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)) {
|
||||
if(bad_ble->hid->is_connected(bad_ble->hid_inst)) {
|
||||
worker_state = BadBleStateIdle; // Ready to run
|
||||
} else {
|
||||
worker_state = BadBleStateNotConnected; // USB not connected
|
||||
}
|
||||
} 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: USB not connected
|
||||
uint32_t flags = bad_ble_flags_get(
|
||||
WorkerEvtEnd | WorkerEvtConnect | WorkerEvtDisconnect | WorkerEvtStartStop,
|
||||
FuriWaitForever);
|
||||
|
||||
if(flags & WorkerEvtEnd) {
|
||||
break;
|
||||
} else if(flags & WorkerEvtConnect) {
|
||||
worker_state = BadBleStateIdle; // Ready to run
|
||||
} else if(flags & WorkerEvtStartStop) {
|
||||
worker_state = BadBleStateWillRun; // Will run when USB is connected
|
||||
}
|
||||
bad_ble->st.state = worker_state;
|
||||
|
||||
} else if(worker_state == BadBleStateIdle) { // State: ready to start
|
||||
uint32_t flags = bad_ble_flags_get(
|
||||
WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtDisconnect, FuriWaitForever);
|
||||
|
||||
if(flags & WorkerEvtEnd) {
|
||||
break;
|
||||
} else if(flags & WorkerEvtStartStop) { // 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->stringdelay = 0;
|
||||
bad_ble->defstringdelay = 0;
|
||||
bad_ble->repeat_cnt = 0;
|
||||
bad_ble->key_hold_nb = 0;
|
||||
bad_ble->file_end = false;
|
||||
storage_file_seek(script_file, 0, true);
|
||||
worker_state = BadBleStateRunning;
|
||||
} else if(flags & WorkerEvtDisconnect) {
|
||||
worker_state = BadBleStateNotConnected; // USB disconnected
|
||||
}
|
||||
bad_ble->st.state = worker_state;
|
||||
|
||||
} else if(worker_state == BadBleStateWillRun) { // State: start on connection
|
||||
uint32_t flags = bad_ble_flags_get(
|
||||
WorkerEvtEnd | WorkerEvtConnect | WorkerEvtStartStop, FuriWaitForever);
|
||||
|
||||
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->stringdelay = 0;
|
||||
bad_ble->defstringdelay = 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
|
||||
flags = furi_thread_flags_wait(
|
||||
WorkerEvtEnd | WorkerEvtDisconnect | WorkerEvtStartStop,
|
||||
FuriFlagWaitAny | FuriFlagNoClear,
|
||||
1500);
|
||||
if(flags == (unsigned)FuriFlagErrorTimeout) {
|
||||
// If nothing happened - start script execution
|
||||
worker_state = BadBleStateRunning;
|
||||
} else if(flags & WorkerEvtStartStop) {
|
||||
worker_state = BadBleStateIdle;
|
||||
furi_thread_flags_clear(WorkerEvtStartStop);
|
||||
}
|
||||
} else if(flags & WorkerEvtStartStop) { // 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 | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtDisconnect,
|
||||
FuriFlagWaitAny,
|
||||
delay_cur);
|
||||
|
||||
delay_val -= delay_cur;
|
||||
if(!(flags & FuriFlagError)) {
|
||||
if(flags & WorkerEvtEnd) {
|
||||
break;
|
||||
} else if(flags & WorkerEvtStartStop) {
|
||||
worker_state = BadBleStateIdle; // Stop executing script
|
||||
bad_ble->hid->release_all(bad_ble->hid_inst);
|
||||
} else if(flags & WorkerEvtDisconnect) {
|
||||
worker_state = BadBleStateNotConnected; // USB disconnected
|
||||
bad_ble->hid->release_all(bad_ble->hid_inst);
|
||||
} else if(flags & WorkerEvtPauseResume) {
|
||||
pause_state = BadBleStateRunning;
|
||||
worker_state = BadBleStatePaused; // Pause
|
||||
}
|
||||
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;
|
||||
bad_ble->hid->release_all(bad_ble->hid_inst);
|
||||
} else if(delay_val == SCRIPT_STATE_END) { // End of script
|
||||
delay_val = 0;
|
||||
worker_state = BadBleStateIdle;
|
||||
bad_ble->st.state = BadBleStateDone;
|
||||
bad_ble->hid->release_all(bad_ble->hid_inst);
|
||||
continue;
|
||||
} else if(delay_val == SCRIPT_STATE_STRING_START) { // Start printing string with delays
|
||||
delay_val = bad_ble->defdelay;
|
||||
bad_ble->string_print_pos = 0;
|
||||
worker_state = BadBleStateStringDelay;
|
||||
} else if(delay_val == SCRIPT_STATE_WAIT_FOR_BTN) { // set state to wait for user input
|
||||
worker_state = BadBleStateWaitForBtn;
|
||||
bad_ble->st.state = BadBleStateWaitForBtn; // Show long delays
|
||||
} 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 == BadBleStateWaitForBtn) { // State: Wait for button Press
|
||||
uint32_t flags = bad_ble_flags_get(
|
||||
WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtDisconnect,
|
||||
FuriWaitForever);
|
||||
if(!(flags & FuriFlagError)) {
|
||||
if(flags & WorkerEvtEnd) {
|
||||
break;
|
||||
} else if(flags & WorkerEvtStartStop) {
|
||||
delay_val = 0;
|
||||
worker_state = BadBleStateRunning;
|
||||
} else if(flags & WorkerEvtDisconnect) {
|
||||
worker_state = BadBleStateNotConnected; // USB disconnected
|
||||
bad_ble->hid->release_all(bad_ble->hid_inst);
|
||||
}
|
||||
bad_ble->st.state = worker_state;
|
||||
continue;
|
||||
}
|
||||
} else if(worker_state == BadBleStatePaused) { // State: Paused
|
||||
uint32_t flags = bad_ble_flags_get(
|
||||
WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtDisconnect,
|
||||
FuriWaitForever);
|
||||
if(!(flags & FuriFlagError)) {
|
||||
if(flags & WorkerEvtEnd) {
|
||||
break;
|
||||
} else if(flags & WorkerEvtStartStop) {
|
||||
worker_state = BadBleStateIdle; // Stop executing script
|
||||
bad_ble->st.state = worker_state;
|
||||
bad_ble->hid->release_all(bad_ble->hid_inst);
|
||||
} else if(flags & WorkerEvtDisconnect) {
|
||||
worker_state = BadBleStateNotConnected; // USB disconnected
|
||||
bad_ble->st.state = worker_state;
|
||||
bad_ble->hid->release_all(bad_ble->hid_inst);
|
||||
} else if(flags & WorkerEvtPauseResume) {
|
||||
if(pause_state == BadBleStateRunning) {
|
||||
if(delay_val > 0) {
|
||||
bad_ble->st.state = BadBleStateDelay;
|
||||
bad_ble->st.delay_remain = delay_val / 1000;
|
||||
} else {
|
||||
bad_ble->st.state = BadBleStateRunning;
|
||||
delay_val = 0;
|
||||
}
|
||||
worker_state = BadBleStateRunning; // Resume
|
||||
} else if(pause_state == BadBleStateStringDelay) {
|
||||
bad_ble->st.state = BadBleStateRunning;
|
||||
worker_state = BadBleStateStringDelay; // Resume
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
} else if(worker_state == BadBleStateStringDelay) { // State: print string with delays
|
||||
uint32_t delay = (bad_ble->stringdelay == 0) ? bad_ble->defstringdelay :
|
||||
bad_ble->stringdelay;
|
||||
uint32_t flags = bad_ble_flags_get(
|
||||
WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtDisconnect,
|
||||
delay);
|
||||
|
||||
if(!(flags & FuriFlagError)) {
|
||||
if(flags & WorkerEvtEnd) {
|
||||
break;
|
||||
} else if(flags & WorkerEvtStartStop) {
|
||||
worker_state = BadBleStateIdle; // Stop executing script
|
||||
bad_ble->hid->release_all(bad_ble->hid_inst);
|
||||
} else if(flags & WorkerEvtDisconnect) {
|
||||
worker_state = BadBleStateNotConnected; // USB disconnected
|
||||
bad_ble->hid->release_all(bad_ble->hid_inst);
|
||||
} else if(flags & WorkerEvtPauseResume) {
|
||||
pause_state = BadBleStateStringDelay;
|
||||
worker_state = BadBleStatePaused; // Pause
|
||||
}
|
||||
bad_ble->st.state = worker_state;
|
||||
continue;
|
||||
} else if(
|
||||
(flags == (unsigned)FuriFlagErrorTimeout) ||
|
||||
(flags == (unsigned)FuriFlagErrorResource)) {
|
||||
bool string_end = ducky_string_next(bad_ble);
|
||||
if(string_end) {
|
||||
bad_ble->stringdelay = 0;
|
||||
worker_state = BadBleStateRunning;
|
||||
}
|
||||
} else {
|
||||
furi_check((flags & FuriFlagError) == 0);
|
||||
}
|
||||
} else if(
|
||||
(worker_state == BadBleStateFileError) ||
|
||||
(worker_state == BadBleStateScriptError)) { // State: error
|
||||
uint32_t flags =
|
||||
bad_ble_flags_get(WorkerEvtEnd, FuriWaitForever); // Waiting for exit command
|
||||
|
||||
if(flags & WorkerEvtEnd) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bad_ble->hid->set_state_callback(bad_ble->hid_inst, NULL, NULL);
|
||||
bad_ble->hid->deinit(bad_ble->hid_inst);
|
||||
|
||||
storage_file_close(script_file);
|
||||
storage_file_free(script_file);
|
||||
furi_string_free(bad_ble->line);
|
||||
furi_string_free(bad_ble->line_prev);
|
||||
furi_string_free(bad_ble->string_print);
|
||||
|
||||
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, BadBleHidInterface interface) {
|
||||
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->hid = bad_ble_hid_get_interface(interface);
|
||||
|
||||
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_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)) { //-V1051
|
||||
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_start_stop(BadBleScript* bad_ble) {
|
||||
furi_assert(bad_ble);
|
||||
furi_thread_flags_set(furi_thread_get_id(bad_ble->thread), WorkerEvtStartStop);
|
||||
}
|
||||
|
||||
void bad_ble_script_pause_resume(BadBleScript* bad_ble) {
|
||||
furi_assert(bad_ble);
|
||||
furi_thread_flags_set(furi_thread_get_id(bad_ble->thread), WorkerEvtPauseResume);
|
||||
}
|
||||
|
||||
BadBleState* bad_ble_script_get_state(BadBleScript* bad_ble) {
|
||||
furi_assert(bad_ble);
|
||||
return &(bad_ble->st);
|
||||
}
|
||||
55
applications/system/bad_ble/helpers/ducky_script.h
Normal file
55
applications/system/bad_ble/helpers/ducky_script.h
Normal file
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include "bad_ble_hid.h"
|
||||
|
||||
typedef enum {
|
||||
BadBleStateInit,
|
||||
BadBleStateNotConnected,
|
||||
BadBleStateIdle,
|
||||
BadBleStateWillRun,
|
||||
BadBleStateRunning,
|
||||
BadBleStateDelay,
|
||||
BadBleStateStringDelay,
|
||||
BadBleStateWaitForBtn,
|
||||
BadBleStatePaused,
|
||||
BadBleStateDone,
|
||||
BadBleStateScriptError,
|
||||
BadBleStateFileError,
|
||||
} BadBleWorkerState;
|
||||
|
||||
typedef struct {
|
||||
BadBleWorkerState state;
|
||||
size_t line_cur;
|
||||
size_t line_nb;
|
||||
uint32_t delay_remain;
|
||||
size_t error_line;
|
||||
char error[64];
|
||||
} BadBleState;
|
||||
|
||||
typedef struct BadBleScript BadBleScript;
|
||||
|
||||
BadBleScript* bad_ble_script_open(FuriString* file_path, BadBleHidInterface interface);
|
||||
|
||||
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_start_stop(BadBleScript* bad_ble);
|
||||
|
||||
void bad_ble_script_pause_resume(BadBleScript* bad_ble);
|
||||
|
||||
BadBleState* bad_ble_script_get_state(BadBleScript* bad_ble);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
241
applications/system/bad_ble/helpers/ducky_script_commands.c
Normal file
241
applications/system/bad_ble/helpers/ducky_script_commands.c
Normal file
@@ -0,0 +1,241 @@
|
||||
#include <furi_hal.h>
|
||||
#include "ducky_script.h"
|
||||
#include "ducky_script_i.h"
|
||||
|
||||
typedef int32_t (*DuckyCmdCallback)(BadBleScript* bad_usb, const char* line, int32_t param);
|
||||
|
||||
typedef struct {
|
||||
char* name;
|
||||
DuckyCmdCallback callback;
|
||||
int32_t param;
|
||||
} DuckyCmd;
|
||||
|
||||
static int32_t ducky_fnc_delay(BadBleScript* bad_usb, 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_usb, "Invalid number %s", line);
|
||||
}
|
||||
|
||||
static int32_t ducky_fnc_defdelay(BadBleScript* bad_usb, const char* line, int32_t param) {
|
||||
UNUSED(param);
|
||||
|
||||
line = &line[ducky_get_command_len(line) + 1];
|
||||
bool state = ducky_get_number(line, &bad_usb->defdelay);
|
||||
if(!state) {
|
||||
return ducky_error(bad_usb, "Invalid number %s", line);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int32_t ducky_fnc_strdelay(BadBleScript* bad_usb, const char* line, int32_t param) {
|
||||
UNUSED(param);
|
||||
|
||||
line = &line[ducky_get_command_len(line) + 1];
|
||||
bool state = ducky_get_number(line, &bad_usb->stringdelay);
|
||||
if(!state) {
|
||||
return ducky_error(bad_usb, "Invalid number %s", line);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int32_t ducky_fnc_defstrdelay(BadBleScript* bad_usb, const char* line, int32_t param) {
|
||||
UNUSED(param);
|
||||
|
||||
line = &line[ducky_get_command_len(line) + 1];
|
||||
bool state = ducky_get_number(line, &bad_usb->defstringdelay);
|
||||
if(!state) {
|
||||
return ducky_error(bad_usb, "Invalid number %s", line);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int32_t ducky_fnc_string(BadBleScript* bad_usb, const char* line, int32_t param) {
|
||||
line = &line[ducky_get_command_len(line) + 1];
|
||||
furi_string_set_str(bad_usb->string_print, line);
|
||||
if(param == 1) {
|
||||
furi_string_cat(bad_usb->string_print, "\n");
|
||||
}
|
||||
|
||||
if(bad_usb->stringdelay == 0 &&
|
||||
bad_usb->defstringdelay == 0) { // stringdelay not set - run command immediately
|
||||
bool state = ducky_string(bad_usb, furi_string_get_cstr(bad_usb->string_print));
|
||||
if(!state) {
|
||||
return ducky_error(bad_usb, "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(BadBleScript* bad_usb, const char* line, int32_t param) {
|
||||
UNUSED(param);
|
||||
|
||||
line = &line[ducky_get_command_len(line) + 1];
|
||||
bool state = ducky_get_number(line, &bad_usb->repeat_cnt);
|
||||
if((!state) || (bad_usb->repeat_cnt == 0)) {
|
||||
return ducky_error(bad_usb, "Invalid number %s", line);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int32_t ducky_fnc_sysrq(BadBleScript* bad_usb, const char* line, int32_t param) {
|
||||
UNUSED(param);
|
||||
|
||||
line = &line[ducky_get_command_len(line) + 1];
|
||||
uint16_t key = ducky_get_keycode(bad_usb, line, true);
|
||||
bad_usb->hid->kb_press(bad_usb->hid_inst, KEY_MOD_LEFT_ALT | HID_KEYBOARD_PRINT_SCREEN);
|
||||
bad_usb->hid->kb_press(bad_usb->hid_inst, key);
|
||||
bad_usb->hid->release_all(bad_usb->hid_inst);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int32_t ducky_fnc_altchar(BadBleScript* bad_usb, const char* line, int32_t param) {
|
||||
UNUSED(param);
|
||||
|
||||
line = &line[ducky_get_command_len(line) + 1];
|
||||
ducky_numlock_on(bad_usb);
|
||||
bool state = ducky_altchar(bad_usb, line);
|
||||
if(!state) {
|
||||
return ducky_error(bad_usb, "Invalid altchar %s", line);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int32_t ducky_fnc_altstring(BadBleScript* bad_usb, const char* line, int32_t param) {
|
||||
UNUSED(param);
|
||||
|
||||
line = &line[ducky_get_command_len(line) + 1];
|
||||
ducky_numlock_on(bad_usb);
|
||||
bool state = ducky_altstring(bad_usb, line);
|
||||
if(!state) {
|
||||
return ducky_error(bad_usb, "Invalid altstring %s", line);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int32_t ducky_fnc_hold(BadBleScript* bad_usb, const char* line, int32_t param) {
|
||||
UNUSED(param);
|
||||
|
||||
line = &line[ducky_get_command_len(line) + 1];
|
||||
uint16_t key = ducky_get_keycode(bad_usb, line, true);
|
||||
if(key == HID_KEYBOARD_NONE) {
|
||||
return ducky_error(bad_usb, "No keycode defined for %s", line);
|
||||
}
|
||||
bad_usb->key_hold_nb++;
|
||||
if(bad_usb->key_hold_nb > (HID_KB_MAX_KEYS - 1)) {
|
||||
return ducky_error(bad_usb, "Too many keys are hold");
|
||||
}
|
||||
bad_usb->hid->kb_press(bad_usb->hid_inst, key);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int32_t ducky_fnc_release(BadBleScript* bad_usb, const char* line, int32_t param) {
|
||||
UNUSED(param);
|
||||
|
||||
line = &line[ducky_get_command_len(line) + 1];
|
||||
uint16_t key = ducky_get_keycode(bad_usb, line, true);
|
||||
if(key == HID_KEYBOARD_NONE) {
|
||||
return ducky_error(bad_usb, "No keycode defined for %s", line);
|
||||
}
|
||||
if(bad_usb->key_hold_nb == 0) {
|
||||
return ducky_error(bad_usb, "No keys are hold");
|
||||
}
|
||||
bad_usb->key_hold_nb--;
|
||||
bad_usb->hid->kb_release(bad_usb->hid_inst, key);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int32_t ducky_fnc_media(BadBleScript* bad_usb, const char* line, int32_t param) {
|
||||
UNUSED(param);
|
||||
|
||||
line = &line[ducky_get_command_len(line) + 1];
|
||||
uint16_t key = ducky_get_media_keycode_by_name(line);
|
||||
if(key == HID_CONSUMER_UNASSIGNED) {
|
||||
return ducky_error(bad_usb, "No keycode defined for %s", line);
|
||||
}
|
||||
bad_usb->hid->consumer_press(bad_usb->hid_inst, key);
|
||||
bad_usb->hid->consumer_release(bad_usb->hid_inst, key);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int32_t ducky_fnc_globe(BadBleScript* bad_usb, const char* line, int32_t param) {
|
||||
UNUSED(param);
|
||||
|
||||
line = &line[ducky_get_command_len(line) + 1];
|
||||
uint16_t key = ducky_get_keycode(bad_usb, line, true);
|
||||
if(key == HID_KEYBOARD_NONE) {
|
||||
return ducky_error(bad_usb, "No keycode defined for %s", line);
|
||||
}
|
||||
|
||||
bad_usb->hid->consumer_press(bad_usb->hid_inst, HID_CONSUMER_FN_GLOBE);
|
||||
bad_usb->hid->kb_press(bad_usb->hid_inst, key);
|
||||
bad_usb->hid->kb_release(bad_usb->hid_inst, key);
|
||||
bad_usb->hid->consumer_release(bad_usb->hid_inst, HID_CONSUMER_FN_GLOBE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int32_t ducky_fnc_waitforbutton(BadBleScript* bad_usb, const char* line, int32_t param) {
|
||||
UNUSED(param);
|
||||
UNUSED(bad_usb);
|
||||
UNUSED(line);
|
||||
|
||||
return SCRIPT_STATE_WAIT_FOR_BTN;
|
||||
}
|
||||
|
||||
static const DuckyCmd ducky_commands[] = {
|
||||
{"REM", NULL, -1},
|
||||
{"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},
|
||||
{"DEFAULT_STRING_DELAY", ducky_fnc_defstrdelay, -1},
|
||||
{"DEFAULTSTRINGDELAY", ducky_fnc_defstrdelay, -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},
|
||||
{"MEDIA", ducky_fnc_media, -1},
|
||||
{"GLOBE", ducky_fnc_globe, -1},
|
||||
};
|
||||
|
||||
#define TAG "BadBle"
|
||||
|
||||
#define WORKER_TAG TAG "Worker"
|
||||
|
||||
int32_t ducky_execute_cmd(BadBleScript* bad_usb, 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_usb, line, ducky_commands[i].param);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return SCRIPT_STATE_CMD_UNKNOWN;
|
||||
}
|
||||
76
applications/system/bad_ble/helpers/ducky_script_i.h
Normal file
76
applications/system/bad_ble/helpers/ducky_script_i.h
Normal file
@@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include "ducky_script.h"
|
||||
#include "bad_ble_hid.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)
|
||||
|
||||
#define FILE_BUFFER_LEN 16
|
||||
|
||||
struct BadBleScript {
|
||||
FuriHalUsbHidConfig hid_cfg;
|
||||
const BadBleHidApi* hid;
|
||||
void* hid_inst;
|
||||
FuriThread* thread;
|
||||
BadBleState st;
|
||||
|
||||
FuriString* file_path;
|
||||
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;
|
||||
uint32_t defstringdelay;
|
||||
uint16_t layout[128];
|
||||
|
||||
FuriString* line;
|
||||
FuriString* line_prev;
|
||||
uint32_t repeat_cnt;
|
||||
uint8_t key_hold_nb;
|
||||
|
||||
FuriString* string_print;
|
||||
size_t string_print_pos;
|
||||
};
|
||||
|
||||
uint16_t ducky_get_keycode(BadBleScript* bad_usb, 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);
|
||||
|
||||
uint16_t ducky_get_media_keycode_by_name(const char* param);
|
||||
|
||||
bool ducky_get_number(const char* param, uint32_t* val);
|
||||
|
||||
void ducky_numlock_on(BadBleScript* bad_usb);
|
||||
|
||||
bool ducky_numpad_press(BadBleScript* bad_usb, const char num);
|
||||
|
||||
bool ducky_altchar(BadBleScript* bad_usb, const char* charcode);
|
||||
|
||||
bool ducky_altstring(BadBleScript* bad_usb, const char* param);
|
||||
|
||||
bool ducky_string(BadBleScript* bad_usb, const char* param);
|
||||
|
||||
int32_t ducky_execute_cmd(BadBleScript* bad_usb, const char* line);
|
||||
|
||||
int32_t ducky_error(BadBleScript* bad_usb, const char* text, ...);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
133
applications/system/bad_ble/helpers/ducky_script_keycodes.c
Normal file
133
applications/system/bad_ble/helpers/ducky_script_keycodes.c
Normal file
@@ -0,0 +1,133 @@
|
||||
#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},
|
||||
{"F13", HID_KEYBOARD_F13},
|
||||
{"F14", HID_KEYBOARD_F14},
|
||||
{"F15", HID_KEYBOARD_F15},
|
||||
{"F16", HID_KEYBOARD_F16},
|
||||
{"F17", HID_KEYBOARD_F17},
|
||||
{"F18", HID_KEYBOARD_F18},
|
||||
{"F19", HID_KEYBOARD_F19},
|
||||
{"F20", HID_KEYBOARD_F20},
|
||||
{"F21", HID_KEYBOARD_F21},
|
||||
{"F22", HID_KEYBOARD_F22},
|
||||
{"F23", HID_KEYBOARD_F23},
|
||||
{"F24", HID_KEYBOARD_F24},
|
||||
};
|
||||
|
||||
static const DuckyKey ducky_media_keys[] = {
|
||||
{"POWER", HID_CONSUMER_POWER},
|
||||
{"REBOOT", HID_CONSUMER_RESET},
|
||||
{"SLEEP", HID_CONSUMER_SLEEP},
|
||||
{"LOGOFF", HID_CONSUMER_AL_LOGOFF},
|
||||
|
||||
{"EXIT", HID_CONSUMER_AC_EXIT},
|
||||
{"HOME", HID_CONSUMER_AC_HOME},
|
||||
{"BACK", HID_CONSUMER_AC_BACK},
|
||||
{"FORWARD", HID_CONSUMER_AC_FORWARD},
|
||||
{"REFRESH", HID_CONSUMER_AC_REFRESH},
|
||||
|
||||
{"SNAPSHOT", HID_CONSUMER_SNAPSHOT},
|
||||
|
||||
{"PLAY", HID_CONSUMER_PLAY},
|
||||
{"PAUSE", HID_CONSUMER_PAUSE},
|
||||
{"PLAY_PAUSE", HID_CONSUMER_PLAY_PAUSE},
|
||||
{"NEXT_TRACK", HID_CONSUMER_SCAN_NEXT_TRACK},
|
||||
{"PREV_TRACK", HID_CONSUMER_SCAN_PREVIOUS_TRACK},
|
||||
{"STOP", HID_CONSUMER_STOP},
|
||||
{"EJECT", HID_CONSUMER_EJECT},
|
||||
|
||||
{"MUTE", HID_CONSUMER_MUTE},
|
||||
{"VOLUME_UP", HID_CONSUMER_VOLUME_INCREMENT},
|
||||
{"VOLUME_DOWN", HID_CONSUMER_VOLUME_DECREMENT},
|
||||
|
||||
{"FN", HID_CONSUMER_FN_GLOBE},
|
||||
{"BRIGHT_UP", HID_CONSUMER_BRIGHTNESS_INCREMENT},
|
||||
{"BRIGHT_DOWN", HID_CONSUMER_BRIGHTNESS_DECREMENT},
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
uint16_t ducky_get_media_keycode_by_name(const char* param) {
|
||||
for(size_t i = 0; i < COUNT_OF(ducky_media_keys); i++) {
|
||||
size_t key_cmd_len = strlen(ducky_media_keys[i].name);
|
||||
if((strncmp(param, ducky_media_keys[i].name, key_cmd_len) == 0) &&
|
||||
(ducky_is_line_end(param[key_cmd_len]))) {
|
||||
return ducky_media_keys[i].keycode;
|
||||
}
|
||||
}
|
||||
|
||||
return HID_CONSUMER_UNASSIGNED;
|
||||
}
|
||||
BIN
applications/system/bad_ble/icon.png
Normal file
BIN
applications/system/bad_ble/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 96 B |
30
applications/system/bad_ble/scenes/bad_ble_scene.c
Normal file
30
applications/system/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/system/bad_ble/scenes/bad_ble_scene.h
Normal file
29
applications/system/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
|
||||
59
applications/system/bad_ble/scenes/bad_ble_scene_config.c
Normal file
59
applications/system/bad_ble/scenes/bad_ble_scene_config.c
Normal file
@@ -0,0 +1,59 @@
|
||||
#include "../bad_ble_app_i.h"
|
||||
|
||||
enum SubmenuIndex {
|
||||
ConfigIndexKeyboardLayout,
|
||||
ConfigIndexBleUnpair,
|
||||
};
|
||||
|
||||
void bad_ble_scene_config_select_callback(void* context, uint32_t index) {
|
||||
BadBleApp* bad_ble = context;
|
||||
|
||||
view_dispatcher_send_custom_event(bad_ble->view_dispatcher, index);
|
||||
}
|
||||
|
||||
static void draw_menu(BadBleApp* bad_ble) {
|
||||
VariableItemList* var_item_list = bad_ble->var_item_list;
|
||||
|
||||
variable_item_list_reset(var_item_list);
|
||||
|
||||
variable_item_list_add(var_item_list, "Keyboard Layout (Global)", 0, NULL, NULL);
|
||||
|
||||
variable_item_list_add(var_item_list, "Unpair Device", 0, NULL, NULL);
|
||||
}
|
||||
|
||||
void bad_ble_scene_config_on_enter(void* context) {
|
||||
BadBleApp* bad_ble = context;
|
||||
VariableItemList* var_item_list = bad_ble->var_item_list;
|
||||
|
||||
variable_item_list_set_enter_callback(
|
||||
var_item_list, bad_ble_scene_config_select_callback, bad_ble);
|
||||
draw_menu(bad_ble);
|
||||
variable_item_list_set_selected_item(var_item_list, 0);
|
||||
|
||||
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) {
|
||||
consumed = true;
|
||||
if(event.event == ConfigIndexKeyboardLayout) {
|
||||
scene_manager_next_scene(bad_ble->scene_manager, BadBleSceneConfigLayout);
|
||||
} else if(event.event == ConfigIndexBleUnpair) {
|
||||
scene_manager_next_scene(bad_ble->scene_manager, BadBleSceneConfirmUnpair);
|
||||
} else {
|
||||
furi_crash("Unknown key type");
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void bad_ble_scene_config_on_exit(void* context) {
|
||||
BadBleApp* bad_ble = context;
|
||||
VariableItemList* var_item_list = bad_ble->var_item_list;
|
||||
|
||||
variable_item_list_reset(var_item_list);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
ADD_SCENE(bad_ble, file_select, FileSelect)
|
||||
ADD_SCENE(bad_ble, work, Work)
|
||||
ADD_SCENE(bad_ble, error, Error)
|
||||
ADD_SCENE(bad_ble, config, Config)
|
||||
ADD_SCENE(bad_ble, config_layout, ConfigLayout)
|
||||
ADD_SCENE(bad_ble, confirm_unpair, ConfirmUnpair)
|
||||
ADD_SCENE(bad_ble, unpair_done, UnpairDone)
|
||||
@@ -0,0 +1,49 @@
|
||||
#include "../bad_ble_app_i.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);
|
||||
browser_options.base_path = BAD_BLE_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_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)) {
|
||||
scene_manager_search_and_switch_to_previous_scene(bad_ble->scene_manager, BadBleSceneWork);
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
#include "../bad_ble_app_i.h"
|
||||
|
||||
void bad_ble_scene_confirm_unpair_widget_callback(
|
||||
GuiButtonType type,
|
||||
InputType input_type,
|
||||
void* context) {
|
||||
UNUSED(input_type);
|
||||
SceneManagerEvent event = {.type = SceneManagerEventTypeCustom, .event = type};
|
||||
bad_ble_scene_confirm_unpair_on_event(context, event);
|
||||
}
|
||||
|
||||
void bad_ble_scene_confirm_unpair_on_enter(void* context) {
|
||||
BadBleApp* bad_ble = context;
|
||||
Widget* widget = bad_ble->widget;
|
||||
|
||||
widget_add_button_element(
|
||||
widget, GuiButtonTypeLeft, "Cancel", bad_ble_scene_confirm_unpair_widget_callback, context);
|
||||
widget_add_button_element(
|
||||
widget,
|
||||
GuiButtonTypeRight,
|
||||
"Unpair",
|
||||
bad_ble_scene_confirm_unpair_widget_callback,
|
||||
context);
|
||||
|
||||
widget_add_text_box_element(
|
||||
widget, 0, 0, 128, 64, AlignCenter, AlignTop, "\e#Unpair the Device?\e#\n", false);
|
||||
|
||||
view_dispatcher_switch_to_view(bad_ble->view_dispatcher, BadBleAppViewWidget);
|
||||
}
|
||||
|
||||
bool bad_ble_scene_confirm_unpair_on_event(void* context, SceneManagerEvent event) {
|
||||
BadBleApp* bad_ble = context;
|
||||
SceneManager* scene_manager = bad_ble->scene_manager;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
consumed = true;
|
||||
if(event.event == GuiButtonTypeRight) {
|
||||
scene_manager_next_scene(scene_manager, BadBleSceneUnpairDone);
|
||||
} else if(event.event == GuiButtonTypeLeft) {
|
||||
scene_manager_previous_scene(scene_manager);
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void bad_ble_scene_confirm_unpair_on_exit(void* context) {
|
||||
BadBleApp* bad_ble = context;
|
||||
Widget* widget = bad_ble->widget;
|
||||
|
||||
widget_reset(widget);
|
||||
}
|
||||
65
applications/system/bad_ble/scenes/bad_ble_scene_error.c
Normal file
65
applications/system/bad_ble/scenes/bad_ble_scene_error.c
Normal file
@@ -0,0 +1,65 @@
|
||||
#include "../bad_ble_app_i.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;
|
||||
|
||||
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);
|
||||
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, BadBleAppViewWidget);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
#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;
|
||||
|
||||
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)) {
|
||||
scene_manager_next_scene(bad_ble->scene_manager, BadBleSceneWork);
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
#include "../bad_ble_app_i.h"
|
||||
|
||||
static void bad_ble_scene_unpair_done_popup_callback(void* context) {
|
||||
BadBleApp* bad_ble = context;
|
||||
scene_manager_search_and_switch_to_previous_scene(bad_ble->scene_manager, BadBleSceneConfig);
|
||||
}
|
||||
|
||||
void bad_ble_scene_unpair_done_on_enter(void* context) {
|
||||
BadBleApp* bad_ble = context;
|
||||
Popup* popup = bad_ble->popup;
|
||||
|
||||
popup_set_icon(popup, 48, 4, &I_DolphinDone_80x58);
|
||||
popup_set_header(popup, "Done", 20, 19, AlignLeft, AlignBottom);
|
||||
popup_set_callback(popup, bad_ble_scene_unpair_done_popup_callback);
|
||||
popup_set_context(popup, bad_ble);
|
||||
popup_set_timeout(popup, 1000);
|
||||
popup_enable_timeout(popup);
|
||||
|
||||
view_dispatcher_switch_to_view(bad_ble->view_dispatcher, BadBleAppViewPopup);
|
||||
}
|
||||
|
||||
bool bad_ble_scene_unpair_done_on_event(void* context, SceneManagerEvent event) {
|
||||
BadBleApp* bad_ble = context;
|
||||
UNUSED(bad_ble);
|
||||
UNUSED(event);
|
||||
|
||||
bool consumed = false;
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void bad_ble_scene_unpair_done_on_exit(void* context) {
|
||||
BadBleApp* bad_ble = context;
|
||||
Popup* popup = bad_ble->popup;
|
||||
|
||||
popup_reset(popup);
|
||||
}
|
||||
65
applications/system/bad_ble/scenes/bad_ble_scene_work.c
Normal file
65
applications/system/bad_ble/scenes/bad_ble_scene_work.c
Normal file
@@ -0,0 +1,65 @@
|
||||
#include "../helpers/ducky_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) {
|
||||
if(bad_ble_view_is_idle_state(app->bad_ble_view)) {
|
||||
bad_ble_script_close(app->bad_ble_script);
|
||||
app->bad_ble_script = NULL;
|
||||
|
||||
scene_manager_next_scene(app->scene_manager, BadBleSceneConfig);
|
||||
}
|
||||
consumed = true;
|
||||
} else if(event.event == InputKeyOk) {
|
||||
bad_ble_script_start_stop(app->bad_ble_script);
|
||||
consumed = true;
|
||||
} else if(event.event == InputKeyRight) {
|
||||
bad_ble_script_pause_resume(app->bad_ble_script);
|
||||
consumed = true;
|
||||
}
|
||||
} else if(event.type == SceneManagerEventTypeTick) {
|
||||
bad_ble_view_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;
|
||||
|
||||
app->bad_ble_script = bad_ble_script_open(app->file_path, app->interface);
|
||||
bad_ble_script_set_keyboard_layout(app->bad_ble_script, app->keyboard_layout);
|
||||
|
||||
FuriString* file_name;
|
||||
file_name = furi_string_alloc();
|
||||
path_extract_filename(app->file_path, file_name, true);
|
||||
bad_ble_view_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_view_set_layout(app->bad_ble_view, furi_string_get_cstr(layout));
|
||||
furi_string_free(layout);
|
||||
|
||||
bad_ble_view_set_state(app->bad_ble_view, bad_ble_script_get_state(app->bad_ble_script));
|
||||
|
||||
bad_ble_view_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);
|
||||
}
|
||||
284
applications/system/bad_ble/views/bad_ble_view.c
Normal file
284
applications/system/bad_ble/views/bad_ble_view.c
Normal file
@@ -0,0 +1,284 @@
|
||||
#include "bad_ble_view.h"
|
||||
#include "../helpers/ducky_script.h"
|
||||
#include <toolbox/path.h>
|
||||
#include <gui/elements.h>
|
||||
#include <assets_icons.h>
|
||||
#include "bad_ble_icons.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;
|
||||
bool pause_wait;
|
||||
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));
|
||||
|
||||
if(strlen(model->layout) == 0) {
|
||||
furi_string_set(disp_str, "(default)");
|
||||
} else {
|
||||
furi_string_printf(disp_str, "(%s)", model->layout);
|
||||
}
|
||||
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_Bad_BLE_48x22);
|
||||
|
||||
BadBleWorkerState state = model->state.state;
|
||||
|
||||
if((state == BadBleStateIdle) || (state == BadBleStateDone) ||
|
||||
(state == BadBleStateNotConnected)) {
|
||||
elements_button_center(canvas, "Run");
|
||||
elements_button_left(canvas, "Config");
|
||||
} else if((state == BadBleStateRunning) || (state == BadBleStateDelay)) {
|
||||
elements_button_center(canvas, "Stop");
|
||||
if(!model->pause_wait) {
|
||||
elements_button_right(canvas, "Pause");
|
||||
}
|
||||
} else if(state == BadBleStatePaused) {
|
||||
elements_button_center(canvas, "End");
|
||||
elements_button_right(canvas, "Resume");
|
||||
} else if(state == BadBleStateWaitForBtn) {
|
||||
elements_button_center(canvas, "Press to continue");
|
||||
} else if(state == BadBleStateWillRun) {
|
||||
elements_button_center(canvas, "Cancel");
|
||||
}
|
||||
|
||||
if(state == BadBleStateNotConnected) {
|
||||
canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18);
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Connect");
|
||||
canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "to device");
|
||||
} else if(state == BadBleStateWillRun) {
|
||||
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(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(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 %zu", 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(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(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, "%zu", ((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(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(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, "%zu", ((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 if((state == BadBleStatePaused) || (state == BadBleStateWaitForBtn)) {
|
||||
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, "%zu", ((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);
|
||||
canvas_draw_str_aligned(canvas, 127, 50, AlignRight, AlignBottom, "Paused");
|
||||
furi_string_reset(disp_str);
|
||||
} else {
|
||||
canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18);
|
||||
}
|
||||
|
||||
furi_string_free(disp_str);
|
||||
}
|
||||
|
||||
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) {
|
||||
consumed = true;
|
||||
furi_assert(bad_ble->callback);
|
||||
bad_ble->callback(event->key, bad_ble->context);
|
||||
} else if(event->key == InputKeyOk) {
|
||||
with_view_model(
|
||||
bad_ble->view, BadBleModel * model, { model->pause_wait = false; }, true);
|
||||
consumed = true;
|
||||
furi_assert(bad_ble->callback);
|
||||
bad_ble->callback(event->key, bad_ble->context);
|
||||
} else if(event->key == InputKeyRight) {
|
||||
with_view_model(
|
||||
bad_ble->view,
|
||||
BadBleModel * model,
|
||||
{
|
||||
if((model->state.state == BadBleStateRunning) ||
|
||||
(model->state.state == BadBleStateDelay)) {
|
||||
model->pause_wait = true;
|
||||
}
|
||||
},
|
||||
true);
|
||||
consumed = true;
|
||||
furi_assert(bad_ble->callback);
|
||||
bad_ble->callback(event->key, bad_ble->context);
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
BadBle* bad_ble_view_alloc(void) {
|
||||
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_view_free(BadBle* bad_ble) {
|
||||
furi_assert(bad_ble);
|
||||
view_free(bad_ble->view);
|
||||
free(bad_ble);
|
||||
}
|
||||
|
||||
View* bad_ble_view_get_view(BadBle* bad_ble) {
|
||||
furi_assert(bad_ble);
|
||||
return bad_ble->view;
|
||||
}
|
||||
|
||||
void bad_ble_view_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_view_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_view_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_view_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;
|
||||
if(model->state.state == BadBleStatePaused) {
|
||||
model->pause_wait = false;
|
||||
}
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
||||
bool bad_ble_view_is_idle_state(BadBle* bad_ble) {
|
||||
bool is_idle = false;
|
||||
with_view_model(
|
||||
bad_ble->view,
|
||||
BadBleModel * model,
|
||||
{
|
||||
if((model->state.state == BadBleStateIdle) ||
|
||||
(model->state.state == BadBleStateDone) ||
|
||||
(model->state.state == BadBleStateNotConnected)) {
|
||||
is_idle = true;
|
||||
}
|
||||
},
|
||||
false);
|
||||
return is_idle;
|
||||
}
|
||||
26
applications/system/bad_ble/views/bad_ble_view.h
Normal file
26
applications/system/bad_ble/views/bad_ble_view.h
Normal file
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
#include "../helpers/ducky_script.h"
|
||||
|
||||
typedef struct BadBle BadBle;
|
||||
typedef void (*BadBleButtonCallback)(InputKey key, void* context);
|
||||
|
||||
BadBle* bad_ble_view_alloc(void);
|
||||
|
||||
void bad_ble_view_free(BadBle* bad_ble);
|
||||
|
||||
View* bad_ble_view_get_view(BadBle* bad_ble);
|
||||
|
||||
void bad_ble_view_set_button_callback(
|
||||
BadBle* bad_ble,
|
||||
BadBleButtonCallback callback,
|
||||
void* context);
|
||||
|
||||
void bad_ble_view_set_file_name(BadBle* bad_ble, const char* name);
|
||||
|
||||
void bad_ble_view_set_layout(BadBle* bad_ble, const char* layout);
|
||||
|
||||
void bad_ble_view_set_state(BadBle* bad_ble, BadBleState* st);
|
||||
|
||||
bool bad_ble_view_is_idle_state(BadBle* bad_ble);
|
||||
@@ -7,7 +7,7 @@
|
||||
#define TAG "HidMouseClicker"
|
||||
|
||||
#define DEFAULT_CLICK_RATE 1
|
||||
#define MAXIMUM_CLICK_RATE 60
|
||||
#define MAXIMUM_CLICK_RATE 100
|
||||
|
||||
struct HidMouseClicker {
|
||||
View* view;
|
||||
@@ -34,7 +34,9 @@ static void hid_mouse_clicker_start_or_restart_timer(void* context) {
|
||||
HidMouseClickerModel * model,
|
||||
{
|
||||
furi_timer_start(
|
||||
hid_mouse_clicker->timer, furi_kernel_get_tick_frequency() / model->rate);
|
||||
hid_mouse_clicker->timer,
|
||||
furi_kernel_get_tick_frequency() /
|
||||
((model->rate) ? model->rate : MAXIMUM_CLICK_RATE));
|
||||
},
|
||||
true);
|
||||
}
|
||||
@@ -75,7 +77,11 @@ static void hid_mouse_clicker_draw_callback(Canvas* canvas, void* context) {
|
||||
|
||||
// Clicks/s
|
||||
char label[20];
|
||||
snprintf(label, sizeof(label), "%d clicks/s", model->rate);
|
||||
if(model->rate) {
|
||||
snprintf(label, sizeof(label), "%d clicks/s", model->rate);
|
||||
} else {
|
||||
snprintf(label, sizeof(label), "max clicks/s");
|
||||
}
|
||||
elements_multiline_text_aligned(canvas, 28, 37, AlignCenter, AlignBottom, label);
|
||||
|
||||
canvas_draw_icon(canvas, 25, 20, &I_ButtonUp_7x4);
|
||||
@@ -139,7 +145,7 @@ static bool hid_mouse_clicker_input_callback(InputEvent* event, void* context) {
|
||||
consumed = true;
|
||||
break;
|
||||
case InputKeyDown:
|
||||
if(model->rate > 1) {
|
||||
if(model->rate > 0) {
|
||||
model->rate--;
|
||||
}
|
||||
rate_changed = true;
|
||||
|
||||
@@ -395,14 +395,15 @@ bool update_task_open_file(UpdateTask* update_task, FuriString* filename) {
|
||||
return open_success;
|
||||
}
|
||||
|
||||
static void update_task_worker_thread_cb(FuriThreadState state, void* context) {
|
||||
UpdateTask* update_task = context;
|
||||
static void
|
||||
update_task_worker_thread_cb(FuriThread* thread, FuriThreadState state, void* context) {
|
||||
UNUSED(context);
|
||||
|
||||
if(state != FuriThreadStateStopped) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(furi_thread_get_return_code(update_task->thread) == UPDATE_TASK_NOERR) {
|
||||
if(furi_thread_get_return_code(thread) == UPDATE_TASK_NOERR) {
|
||||
furi_delay_ms(UPDATE_DELAY_OPERATION_OK);
|
||||
furi_hal_power_reset();
|
||||
}
|
||||
@@ -427,7 +428,6 @@ UpdateTask* update_task_alloc(void) {
|
||||
furi_thread_alloc_ex("UpdateWorker", 5120, NULL, update_task);
|
||||
|
||||
furi_thread_set_state_callback(thread, update_task_worker_thread_cb);
|
||||
furi_thread_set_state_context(thread, update_task);
|
||||
#ifdef FURI_RAM_EXEC
|
||||
UNUSED(update_task_worker_backup_restore);
|
||||
furi_thread_set_callback(thread, update_task_worker_flash_writer);
|
||||
|
||||
Reference in New Issue
Block a user