mirror of
https://github.com/DarkFlippers/unleashed-firmware
synced 2024-11-26 14:30:25 +00:00
BadUSB: BLE, media keys, Fn/Globe key commands (#3403)
* BadUSB: media keys, GLOBE command * f18 api table fix * BadUSB over BLE * Made PVS happy Co-authored-by: Aleksandr Kutuzov <alleteam@gmail.com>
This commit is contained in:
parent
f633f476c8
commit
6de2934394
22 changed files with 637 additions and 171 deletions
|
@ -7,7 +7,7 @@ App(
|
|||
icon="A_BadUsb_14",
|
||||
order=70,
|
||||
resources="resources",
|
||||
fap_libs=["assets"],
|
||||
fap_libs=["assets", "ble_profile"],
|
||||
fap_icon="icon.png",
|
||||
fap_category="USB",
|
||||
)
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
#include "bad_usb_app_i.h"
|
||||
#include "bad_usb_settings_filename.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_USB_SETTINGS_PATH BAD_USB_APP_BASE_FOLDER "/" BAD_USB_SETTINGS_FILE_NAME
|
||||
#define BAD_USB_SETTINGS_PATH BAD_USB_APP_BASE_FOLDER "/.badusb.settings"
|
||||
#define BAD_USB_SETTINGS_FILE_TYPE "Flipper BadUSB Settings File"
|
||||
#define BAD_USB_SETTINGS_VERSION 1
|
||||
#define BAD_USB_SETTINGS_DEFAULT_LAYOUT BAD_USB_APP_PATH_LAYOUT_FOLDER "/en-US.kl"
|
||||
|
||||
static bool bad_usb_app_custom_event_callback(void* context, uint32_t event) {
|
||||
furi_assert(context);
|
||||
|
@ -26,46 +29,69 @@ static void bad_usb_app_tick_event_callback(void* context) {
|
|||
}
|
||||
|
||||
static void bad_usb_load_settings(BadUsbApp* app) {
|
||||
File* settings_file = storage_file_alloc(furi_record_open(RECORD_STORAGE));
|
||||
if(storage_file_open(settings_file, BAD_USB_SETTINGS_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) {
|
||||
char chr;
|
||||
while((storage_file_read(settings_file, &chr, 1) == 1) &&
|
||||
!storage_file_eof(settings_file) && !isspace(chr)) {
|
||||
furi_string_push_back(app->keyboard_layout, chr);
|
||||
}
|
||||
} else {
|
||||
furi_string_reset(app->keyboard_layout);
|
||||
}
|
||||
storage_file_close(settings_file);
|
||||
storage_file_free(settings_file);
|
||||
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;
|
||||
uint32_t interface = 0;
|
||||
|
||||
if(flipper_format_file_open_existing(fff, BAD_USB_SETTINGS_PATH)) {
|
||||
do {
|
||||
if(!flipper_format_read_header(fff, temp_str, &version)) break;
|
||||
if((strcmp(furi_string_get_cstr(temp_str), BAD_USB_SETTINGS_FILE_TYPE) != 0) ||
|
||||
(version != BAD_USB_SETTINGS_VERSION))
|
||||
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);
|
||||
}
|
||||
flipper_format_free(fff);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
if(state) {
|
||||
furi_string_set(app->keyboard_layout, temp_str);
|
||||
app->interface = interface;
|
||||
|
||||
if(!furi_string_empty(app->keyboard_layout)) {
|
||||
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) {
|
||||
furi_string_reset(app->keyboard_layout);
|
||||
return;
|
||||
}
|
||||
if(layout_file_info.size != 256) {
|
||||
furi_string_reset(app->keyboard_layout);
|
||||
if((file_check_err != FSE_OK) || (layout_file_info.size != 256)) {
|
||||
furi_string_set(app->keyboard_layout, BAD_USB_SETTINGS_DEFAULT_LAYOUT);
|
||||
}
|
||||
} else {
|
||||
furi_string_set(app->keyboard_layout, BAD_USB_SETTINGS_DEFAULT_LAYOUT);
|
||||
app->interface = BadUsbHidInterfaceUsb;
|
||||
}
|
||||
|
||||
furi_string_free(temp_str);
|
||||
}
|
||||
|
||||
static void bad_usb_save_settings(BadUsbApp* app) {
|
||||
File* settings_file = storage_file_alloc(furi_record_open(RECORD_STORAGE));
|
||||
if(storage_file_open(settings_file, BAD_USB_SETTINGS_PATH, FSAM_WRITE, FSOM_OPEN_ALWAYS)) {
|
||||
storage_file_write(
|
||||
settings_file,
|
||||
furi_string_get_cstr(app->keyboard_layout),
|
||||
furi_string_size(app->keyboard_layout));
|
||||
storage_file_write(settings_file, "\n", 1);
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
FlipperFormat* fff = flipper_format_file_alloc(storage);
|
||||
|
||||
if(flipper_format_file_open_always(fff, BAD_USB_SETTINGS_PATH)) {
|
||||
do {
|
||||
if(!flipper_format_write_header_cstr(
|
||||
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);
|
||||
}
|
||||
storage_file_close(settings_file);
|
||||
storage_file_free(settings_file);
|
||||
|
||||
flipper_format_free(fff);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
BadUsbApp* bad_usb_app_alloc(char* arg) {
|
||||
|
@ -103,13 +129,15 @@ BadUsbApp* bad_usb_app_alloc(char* arg) {
|
|||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, BadUsbAppViewError, widget_get_view(app->widget));
|
||||
|
||||
app->submenu = submenu_alloc();
|
||||
app->var_item_list = variable_item_list_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, BadUsbAppViewConfig, submenu_get_view(app->submenu));
|
||||
app->view_dispatcher,
|
||||
BadUsbAppViewConfig,
|
||||
variable_item_list_get_view(app->var_item_list));
|
||||
|
||||
app->bad_usb_view = bad_usb_alloc();
|
||||
app->bad_usb_view = bad_usb_view_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, BadUsbAppViewWork, bad_usb_get_view(app->bad_usb_view));
|
||||
app->view_dispatcher, BadUsbAppViewWork, bad_usb_view_get_view(app->bad_usb_view));
|
||||
|
||||
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||
|
||||
|
@ -122,8 +150,6 @@ BadUsbApp* bad_usb_app_alloc(char* arg) {
|
|||
furi_check(furi_hal_usb_set_config(NULL, NULL));
|
||||
|
||||
if(!furi_string_empty(app->file_path)) {
|
||||
app->bad_usb_script = bad_usb_script_open(app->file_path);
|
||||
bad_usb_script_set_keyboard_layout(app->bad_usb_script, app->keyboard_layout);
|
||||
scene_manager_next_scene(app->scene_manager, BadUsbSceneWork);
|
||||
} else {
|
||||
furi_string_set(app->file_path, BAD_USB_APP_BASE_FOLDER);
|
||||
|
@ -144,15 +170,15 @@ void bad_usb_app_free(BadUsbApp* app) {
|
|||
|
||||
// Views
|
||||
view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewWork);
|
||||
bad_usb_free(app->bad_usb_view);
|
||||
bad_usb_view_free(app->bad_usb_view);
|
||||
|
||||
// Custom Widget
|
||||
view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewError);
|
||||
widget_free(app->widget);
|
||||
|
||||
// Submenu
|
||||
// Config menu
|
||||
view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewConfig);
|
||||
submenu_free(app->submenu);
|
||||
variable_item_list_free(app->var_item_list);
|
||||
|
||||
// View dispatcher
|
||||
view_dispatcher_free(app->view_dispatcher);
|
||||
|
|
|
@ -3,12 +3,12 @@
|
|||
#include "bad_usb_app.h"
|
||||
#include "scenes/bad_usb_scene.h"
|
||||
#include "helpers/ducky_script.h"
|
||||
#include "helpers/bad_usb_hid.h"
|
||||
|
||||
#include <gui/gui.h>
|
||||
#include <assets_icons.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <gui/scene_manager.h>
|
||||
#include <gui/modules/submenu.h>
|
||||
#include <dialogs/dialogs.h>
|
||||
#include <notification/notification_messages.h>
|
||||
#include <gui/modules/variable_item_list.h>
|
||||
|
@ -33,7 +33,7 @@ struct BadUsbApp {
|
|||
NotificationApp* notifications;
|
||||
DialogsApp* dialogs;
|
||||
Widget* widget;
|
||||
Submenu* submenu;
|
||||
VariableItemList* var_item_list;
|
||||
|
||||
BadUsbAppError error;
|
||||
FuriString* file_path;
|
||||
|
@ -41,6 +41,7 @@ struct BadUsbApp {
|
|||
BadUsb* bad_usb_view;
|
||||
BadUsbScript* bad_usb_script;
|
||||
|
||||
BadUsbHidInterface interface;
|
||||
FuriHalUsbInterface* usb_if_prev;
|
||||
};
|
||||
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#define BAD_USB_SETTINGS_FILE_NAME ".badusb.settings"
|
226
applications/main/bad_usb/helpers/bad_usb_hid.c
Normal file
226
applications/main/bad_usb/helpers/bad_usb_hid.c
Normal file
|
@ -0,0 +1,226 @@
|
|||
#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;
|
||||
}
|
||||
|
||||
void hid_usb_deinit(void* inst) {
|
||||
UNUSED(inst);
|
||||
furi_check(furi_hal_usb_set_config(NULL, NULL));
|
||||
}
|
||||
|
||||
void hid_usb_set_state_callback(void* inst, HidStateCallback cb, void* context) {
|
||||
UNUSED(inst);
|
||||
furi_hal_hid_set_state_callback(cb, context);
|
||||
}
|
||||
|
||||
bool hid_usb_is_connected(void* inst) {
|
||||
UNUSED(inst);
|
||||
return furi_hal_hid_is_connected();
|
||||
}
|
||||
|
||||
bool hid_usb_kb_press(void* inst, uint16_t button) {
|
||||
UNUSED(inst);
|
||||
return furi_hal_hid_kb_press(button);
|
||||
}
|
||||
|
||||
bool hid_usb_kb_release(void* inst, uint16_t button) {
|
||||
UNUSED(inst);
|
||||
return furi_hal_hid_kb_release(button);
|
||||
}
|
||||
|
||||
bool hid_usb_consumer_press(void* inst, uint16_t button) {
|
||||
UNUSED(inst);
|
||||
return furi_hal_hid_consumer_key_press(button);
|
||||
}
|
||||
|
||||
bool hid_usb_consumer_release(void* inst, uint16_t button) {
|
||||
UNUSED(inst);
|
||||
return furi_hal_hid_consumer_key_release(button);
|
||||
}
|
||||
|
||||
bool hid_usb_release_all(void* inst) {
|
||||
UNUSED(inst);
|
||||
bool state = furi_hal_hid_kb_release_all();
|
||||
state &= furi_hal_hid_consumer_key_release_all();
|
||||
return state;
|
||||
}
|
||||
|
||||
uint8_t hid_usb_get_led_state(void* inst) {
|
||||
UNUSED(inst);
|
||||
return furi_hal_hid_get_led_state();
|
||||
}
|
||||
|
||||
static const BadUsbHidApi hid_api_usb = {
|
||||
.init = hid_usb_init,
|
||||
.deinit = hid_usb_deinit,
|
||||
.set_state_callback = hid_usb_set_state_callback,
|
||||
.is_connected = hid_usb_is_connected,
|
||||
|
||||
.kb_press = hid_usb_kb_press,
|
||||
.kb_release = hid_usb_kb_release,
|
||||
.consumer_press = hid_usb_consumer_press,
|
||||
.consumer_release = hid_usb_consumer_release,
|
||||
.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);
|
||||
}
|
35
applications/main/bad_usb/helpers/bad_usb_hid.h
Normal file
35
applications/main/bad_usb/helpers/bad_usb_hid.h
Normal file
|
@ -0,0 +1,35 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
|
||||
typedef enum {
|
||||
BadUsbHidInterfaceUsb,
|
||||
BadUsbHidInterfaceBle,
|
||||
} BadUsbHidInterface;
|
||||
|
||||
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);
|
||||
} BadUsbHidApi;
|
||||
|
||||
const BadUsbHidApi* bad_usb_hid_get_interface(BadUsbHidInterface interface);
|
||||
|
||||
void bad_usb_hid_ble_remove_pairing(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
|
@ -3,7 +3,6 @@
|
|||
#include <gui/gui.h>
|
||||
#include <input/input.h>
|
||||
#include <lib/toolbox/args.h>
|
||||
#include <furi_hal_usb_hid.h>
|
||||
#include <storage/storage.h>
|
||||
#include "ducky_script.h"
|
||||
#include "ducky_script_i.h"
|
||||
|
@ -71,39 +70,40 @@ bool ducky_get_number(const char* param, uint32_t* val) {
|
|||
return false;
|
||||
}
|
||||
|
||||
void ducky_numlock_on(void) {
|
||||
if((furi_hal_hid_get_led_state() & HID_KB_LED_NUM) == 0) {
|
||||
furi_hal_hid_kb_press(HID_KEYBOARD_LOCK_NUM_LOCK);
|
||||
furi_hal_hid_kb_release(HID_KEYBOARD_LOCK_NUM_LOCK);
|
||||
void ducky_numlock_on(BadUsbScript* bad_usb) {
|
||||
if((bad_usb->hid->get_led_state(bad_usb->hid_inst) & HID_KB_LED_NUM) == 0) {
|
||||
bad_usb->hid->kb_press(bad_usb->hid_inst, HID_KEYBOARD_LOCK_NUM_LOCK);
|
||||
bad_usb->hid->kb_release(bad_usb->hid_inst, HID_KEYBOARD_LOCK_NUM_LOCK);
|
||||
}
|
||||
}
|
||||
bool ducky_numpad_press(const char num) {
|
||||
|
||||
bool ducky_numpad_press(BadUsbScript* bad_usb, const char num) {
|
||||
if((num < '0') || (num > '9')) return false;
|
||||
|
||||
uint16_t key = numpad_keys[num - '0'];
|
||||
furi_hal_hid_kb_press(key);
|
||||
furi_hal_hid_kb_release(key);
|
||||
bad_usb->hid->kb_press(bad_usb->hid_inst, key);
|
||||
bad_usb->hid->kb_release(bad_usb->hid_inst, key);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ducky_altchar(const char* charcode) {
|
||||
bool ducky_altchar(BadUsbScript* bad_usb, const char* charcode) {
|
||||
uint8_t i = 0;
|
||||
bool state = false;
|
||||
|
||||
furi_hal_hid_kb_press(KEY_MOD_LEFT_ALT);
|
||||
bad_usb->hid->kb_press(bad_usb->hid_inst, KEY_MOD_LEFT_ALT);
|
||||
|
||||
while(!ducky_is_line_end(charcode[i])) {
|
||||
state = ducky_numpad_press(charcode[i]);
|
||||
state = ducky_numpad_press(bad_usb, charcode[i]);
|
||||
if(state == false) break;
|
||||
i++;
|
||||
}
|
||||
|
||||
furi_hal_hid_kb_release(KEY_MOD_LEFT_ALT);
|
||||
bad_usb->hid->kb_release(bad_usb->hid_inst, KEY_MOD_LEFT_ALT);
|
||||
return state;
|
||||
}
|
||||
|
||||
bool ducky_altstring(const char* param) {
|
||||
bool ducky_altstring(BadUsbScript* bad_usb, const char* param) {
|
||||
uint32_t i = 0;
|
||||
bool state = false;
|
||||
|
||||
|
@ -116,7 +116,7 @@ bool ducky_altstring(const char* param) {
|
|||
char temp_str[4];
|
||||
snprintf(temp_str, 4, "%u", param[i]);
|
||||
|
||||
state = ducky_altchar(temp_str);
|
||||
state = ducky_altchar(bad_usb, temp_str);
|
||||
if(state == false) break;
|
||||
i++;
|
||||
}
|
||||
|
@ -140,12 +140,12 @@ bool ducky_string(BadUsbScript* bad_usb, const char* param) {
|
|||
if(param[i] != '\n') {
|
||||
uint16_t keycode = BADUSB_ASCII_TO_KEY(bad_usb, param[i]);
|
||||
if(keycode != HID_KEYBOARD_NONE) {
|
||||
furi_hal_hid_kb_press(keycode);
|
||||
furi_hal_hid_kb_release(keycode);
|
||||
bad_usb->hid->kb_press(bad_usb->hid_inst, keycode);
|
||||
bad_usb->hid->kb_release(bad_usb->hid_inst, keycode);
|
||||
}
|
||||
} else {
|
||||
furi_hal_hid_kb_press(HID_KEYBOARD_RETURN);
|
||||
furi_hal_hid_kb_release(HID_KEYBOARD_RETURN);
|
||||
bad_usb->hid->kb_press(bad_usb->hid_inst, HID_KEYBOARD_RETURN);
|
||||
bad_usb->hid->kb_release(bad_usb->hid_inst, HID_KEYBOARD_RETURN);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
@ -163,12 +163,12 @@ static bool ducky_string_next(BadUsbScript* bad_usb) {
|
|||
if(print_char != '\n') {
|
||||
uint16_t keycode = BADUSB_ASCII_TO_KEY(bad_usb, print_char);
|
||||
if(keycode != HID_KEYBOARD_NONE) {
|
||||
furi_hal_hid_kb_press(keycode);
|
||||
furi_hal_hid_kb_release(keycode);
|
||||
bad_usb->hid->kb_press(bad_usb->hid_inst, keycode);
|
||||
bad_usb->hid->kb_release(bad_usb->hid_inst, keycode);
|
||||
}
|
||||
} else {
|
||||
furi_hal_hid_kb_press(HID_KEYBOARD_RETURN);
|
||||
furi_hal_hid_kb_release(HID_KEYBOARD_RETURN);
|
||||
bad_usb->hid->kb_press(bad_usb->hid_inst, HID_KEYBOARD_RETURN);
|
||||
bad_usb->hid->kb_release(bad_usb->hid_inst, HID_KEYBOARD_RETURN);
|
||||
}
|
||||
|
||||
bad_usb->string_print_pos++;
|
||||
|
@ -201,8 +201,8 @@ static int32_t ducky_parse_line(BadUsbScript* bad_usb, FuriString* line) {
|
|||
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
|
||||
key |= ducky_get_keycode(bad_usb, line_tmp, true);
|
||||
}
|
||||
furi_hal_hid_kb_press(key);
|
||||
furi_hal_hid_kb_release(key);
|
||||
bad_usb->hid->kb_press(bad_usb->hid_inst, key);
|
||||
bad_usb->hid->kb_release(bad_usb->hid_inst, key);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -231,6 +231,17 @@ static bool ducky_set_usb_id(BadUsbScript* bad_usb, const char* line) {
|
|||
return false;
|
||||
}
|
||||
|
||||
static void bad_usb_hid_state_callback(bool state, void* context) {
|
||||
furi_assert(context);
|
||||
BadUsbScript* bad_usb = context;
|
||||
|
||||
if(state == true) {
|
||||
furi_thread_flags_set(furi_thread_get_id(bad_usb->thread), WorkerEvtConnect);
|
||||
} else {
|
||||
furi_thread_flags_set(furi_thread_get_id(bad_usb->thread), WorkerEvtDisconnect);
|
||||
}
|
||||
}
|
||||
|
||||
static bool ducky_script_preload(BadUsbScript* bad_usb, File* script_file) {
|
||||
uint8_t ret = 0;
|
||||
uint32_t line_len = 0;
|
||||
|
@ -265,10 +276,11 @@ static bool ducky_script_preload(BadUsbScript* bad_usb, File* script_file) {
|
|||
}
|
||||
|
||||
if(id_set) {
|
||||
furi_check(furi_hal_usb_set_config(&usb_hid, &bad_usb->hid_cfg));
|
||||
bad_usb->hid_inst = bad_usb->hid->init(&bad_usb->hid_cfg);
|
||||
} else {
|
||||
furi_check(furi_hal_usb_set_config(&usb_hid, NULL));
|
||||
bad_usb->hid_inst = bad_usb->hid->init(NULL);
|
||||
}
|
||||
bad_usb->hid->set_state_callback(bad_usb->hid_inst, bad_usb_hid_state_callback, bad_usb);
|
||||
|
||||
storage_file_seek(script_file, 0, true);
|
||||
furi_string_reset(bad_usb->line);
|
||||
|
@ -345,17 +357,6 @@ static int32_t ducky_script_execute_next(BadUsbScript* bad_usb, File* script_fil
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void bad_usb_hid_state_callback(bool state, void* context) {
|
||||
furi_assert(context);
|
||||
BadUsbScript* bad_usb = context;
|
||||
|
||||
if(state == true) {
|
||||
furi_thread_flags_set(furi_thread_get_id(bad_usb->thread), WorkerEvtConnect);
|
||||
} else {
|
||||
furi_thread_flags_set(furi_thread_get_id(bad_usb->thread), WorkerEvtDisconnect);
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t bad_usb_flags_get(uint32_t flags_mask, uint32_t timeout) {
|
||||
uint32_t flags = furi_thread_flags_get();
|
||||
furi_check((flags & FuriFlagError) == 0);
|
||||
|
@ -382,8 +383,6 @@ static int32_t bad_usb_worker(void* context) {
|
|||
bad_usb->line_prev = furi_string_alloc();
|
||||
bad_usb->string_print = furi_string_alloc();
|
||||
|
||||
furi_hal_hid_set_state_callback(bad_usb_hid_state_callback, bad_usb);
|
||||
|
||||
while(1) {
|
||||
if(worker_state == BadUsbStateInit) { // State: initialization
|
||||
if(storage_file_open(
|
||||
|
@ -392,7 +391,7 @@ static int32_t bad_usb_worker(void* context) {
|
|||
FSAM_READ,
|
||||
FSOM_OPEN_EXISTING)) {
|
||||
if((ducky_script_preload(bad_usb, script_file)) && (bad_usb->st.line_nb > 0)) {
|
||||
if(furi_hal_hid_is_connected()) {
|
||||
if(bad_usb->hid->is_connected(bad_usb->hid_inst)) {
|
||||
worker_state = BadUsbStateIdle; // Ready to run
|
||||
} else {
|
||||
worker_state = BadUsbStateNotConnected; // USB not connected
|
||||
|
@ -408,7 +407,8 @@ static int32_t bad_usb_worker(void* context) {
|
|||
|
||||
} else if(worker_state == BadUsbStateNotConnected) { // State: USB not connected
|
||||
uint32_t flags = bad_usb_flags_get(
|
||||
WorkerEvtEnd | WorkerEvtConnect | WorkerEvtStartStop, FuriWaitForever);
|
||||
WorkerEvtEnd | WorkerEvtConnect | WorkerEvtDisconnect | WorkerEvtStartStop,
|
||||
FuriWaitForever);
|
||||
|
||||
if(flags & WorkerEvtEnd) {
|
||||
break;
|
||||
|
@ -490,10 +490,10 @@ static int32_t bad_usb_worker(void* context) {
|
|||
break;
|
||||
} else if(flags & WorkerEvtStartStop) {
|
||||
worker_state = BadUsbStateIdle; // Stop executing script
|
||||
furi_hal_hid_kb_release_all();
|
||||
bad_usb->hid->release_all(bad_usb->hid_inst);
|
||||
} else if(flags & WorkerEvtDisconnect) {
|
||||
worker_state = BadUsbStateNotConnected; // USB disconnected
|
||||
furi_hal_hid_kb_release_all();
|
||||
bad_usb->hid->release_all(bad_usb->hid_inst);
|
||||
} else if(flags & WorkerEvtPauseResume) {
|
||||
pause_state = BadUsbStateRunning;
|
||||
worker_state = BadUsbStatePaused; // Pause
|
||||
|
@ -513,12 +513,12 @@ static int32_t bad_usb_worker(void* context) {
|
|||
delay_val = 0;
|
||||
worker_state = BadUsbStateScriptError;
|
||||
bad_usb->st.state = worker_state;
|
||||
furi_hal_hid_kb_release_all();
|
||||
bad_usb->hid->release_all(bad_usb->hid_inst);
|
||||
} else if(delay_val == SCRIPT_STATE_END) { // End of script
|
||||
delay_val = 0;
|
||||
worker_state = BadUsbStateIdle;
|
||||
bad_usb->st.state = BadUsbStateDone;
|
||||
furi_hal_hid_kb_release_all();
|
||||
bad_usb->hid->release_all(bad_usb->hid_inst);
|
||||
continue;
|
||||
} else if(delay_val == SCRIPT_STATE_STRING_START) { // Start printing string with delays
|
||||
delay_val = bad_usb->defdelay;
|
||||
|
@ -546,7 +546,7 @@ static int32_t bad_usb_worker(void* context) {
|
|||
worker_state = BadUsbStateRunning;
|
||||
} else if(flags & WorkerEvtDisconnect) {
|
||||
worker_state = BadUsbStateNotConnected; // USB disconnected
|
||||
furi_hal_hid_kb_release_all();
|
||||
bad_usb->hid->release_all(bad_usb->hid_inst);
|
||||
}
|
||||
bad_usb->st.state = worker_state;
|
||||
continue;
|
||||
|
@ -561,11 +561,11 @@ static int32_t bad_usb_worker(void* context) {
|
|||
} else if(flags & WorkerEvtStartStop) {
|
||||
worker_state = BadUsbStateIdle; // Stop executing script
|
||||
bad_usb->st.state = worker_state;
|
||||
furi_hal_hid_kb_release_all();
|
||||
bad_usb->hid->release_all(bad_usb->hid_inst);
|
||||
} else if(flags & WorkerEvtDisconnect) {
|
||||
worker_state = BadUsbStateNotConnected; // USB disconnected
|
||||
bad_usb->st.state = worker_state;
|
||||
furi_hal_hid_kb_release_all();
|
||||
bad_usb->hid->release_all(bad_usb->hid_inst);
|
||||
} else if(flags & WorkerEvtPauseResume) {
|
||||
if(pause_state == BadUsbStateRunning) {
|
||||
if(delay_val > 0) {
|
||||
|
@ -595,10 +595,10 @@ static int32_t bad_usb_worker(void* context) {
|
|||
break;
|
||||
} else if(flags & WorkerEvtStartStop) {
|
||||
worker_state = BadUsbStateIdle; // Stop executing script
|
||||
furi_hal_hid_kb_release_all();
|
||||
bad_usb->hid->release_all(bad_usb->hid_inst);
|
||||
} else if(flags & WorkerEvtDisconnect) {
|
||||
worker_state = BadUsbStateNotConnected; // USB disconnected
|
||||
furi_hal_hid_kb_release_all();
|
||||
bad_usb->hid->release_all(bad_usb->hid_inst);
|
||||
} else if(flags & WorkerEvtPauseResume) {
|
||||
pause_state = BadUsbStateStringDelay;
|
||||
worker_state = BadUsbStatePaused; // Pause
|
||||
|
@ -628,7 +628,8 @@ static int32_t bad_usb_worker(void* context) {
|
|||
}
|
||||
}
|
||||
|
||||
furi_hal_hid_set_state_callback(NULL, NULL);
|
||||
bad_usb->hid->set_state_callback(bad_usb->hid_inst, NULL, NULL);
|
||||
bad_usb->hid->deinit(bad_usb->hid_inst);
|
||||
|
||||
storage_file_close(script_file);
|
||||
storage_file_free(script_file);
|
||||
|
@ -647,7 +648,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) {
|
||||
BadUsbScript* bad_usb_script_open(FuriString* file_path, BadUsbHidInterface interface) {
|
||||
furi_assert(file_path);
|
||||
|
||||
BadUsbScript* bad_usb = malloc(sizeof(BadUsbScript));
|
||||
|
@ -657,6 +658,7 @@ BadUsbScript* bad_usb_script_open(FuriString* file_path) {
|
|||
|
||||
bad_usb->st.state = BadUsbStateInit;
|
||||
bad_usb->st.error[0] = '\0';
|
||||
bad_usb->hid = bad_usb_hid_get_interface(interface);
|
||||
|
||||
bad_usb->thread = furi_thread_alloc_ex("BadUsbWorker", 2048, bad_usb_worker, bad_usb);
|
||||
furi_thread_start(bad_usb->thread);
|
||||
|
|
|
@ -6,6 +6,7 @@ extern "C" {
|
|||
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include "bad_usb_hid.h"
|
||||
|
||||
typedef enum {
|
||||
BadUsbStateInit,
|
||||
|
@ -33,7 +34,7 @@ typedef struct {
|
|||
|
||||
typedef struct BadUsbScript BadUsbScript;
|
||||
|
||||
BadUsbScript* bad_usb_script_open(FuriString* file_path);
|
||||
BadUsbScript* bad_usb_script_open(FuriString* file_path, BadUsbHidInterface interface);
|
||||
|
||||
void bad_usb_script_close(BadUsbScript* bad_usb);
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#include <furi_hal.h>
|
||||
#include <furi_hal_usb_hid.h>
|
||||
#include "ducky_script.h"
|
||||
#include "ducky_script_i.h"
|
||||
|
||||
|
@ -93,9 +92,9 @@ static int32_t ducky_fnc_sysrq(BadUsbScript* bad_usb, const char* line, int32_t
|
|||
|
||||
line = &line[ducky_get_command_len(line) + 1];
|
||||
uint16_t key = ducky_get_keycode(bad_usb, line, true);
|
||||
furi_hal_hid_kb_press(KEY_MOD_LEFT_ALT | HID_KEYBOARD_PRINT_SCREEN);
|
||||
furi_hal_hid_kb_press(key);
|
||||
furi_hal_hid_kb_release_all();
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -103,8 +102,8 @@ static int32_t ducky_fnc_altchar(BadUsbScript* bad_usb, const char* line, int32_
|
|||
UNUSED(param);
|
||||
|
||||
line = &line[ducky_get_command_len(line) + 1];
|
||||
ducky_numlock_on();
|
||||
bool state = ducky_altchar(line);
|
||||
ducky_numlock_on(bad_usb);
|
||||
bool state = ducky_altchar(bad_usb, line);
|
||||
if(!state) {
|
||||
return ducky_error(bad_usb, "Invalid altchar %s", line);
|
||||
}
|
||||
|
@ -115,8 +114,8 @@ static int32_t ducky_fnc_altstring(BadUsbScript* bad_usb, const char* line, int3
|
|||
UNUSED(param);
|
||||
|
||||
line = &line[ducky_get_command_len(line) + 1];
|
||||
ducky_numlock_on();
|
||||
bool state = ducky_altstring(line);
|
||||
ducky_numlock_on(bad_usb);
|
||||
bool state = ducky_altstring(bad_usb, line);
|
||||
if(!state) {
|
||||
return ducky_error(bad_usb, "Invalid altstring %s", line);
|
||||
}
|
||||
|
@ -135,7 +134,7 @@ static int32_t ducky_fnc_hold(BadUsbScript* bad_usb, const char* line, int32_t p
|
|||
if(bad_usb->key_hold_nb > (HID_KB_MAX_KEYS - 1)) {
|
||||
return ducky_error(bad_usb, "Too many keys are hold");
|
||||
}
|
||||
furi_hal_hid_kb_press(key);
|
||||
bad_usb->hid->kb_press(bad_usb->hid_inst, key);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -151,7 +150,36 @@ static int32_t ducky_fnc_release(BadUsbScript* bad_usb, const char* line, int32_
|
|||
return ducky_error(bad_usb, "No keys are hold");
|
||||
}
|
||||
bad_usb->key_hold_nb--;
|
||||
furi_hal_hid_kb_release(key);
|
||||
bad_usb->hid->kb_release(bad_usb->hid_inst, key);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int32_t ducky_fnc_media(BadUsbScript* 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(BadUsbScript* 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;
|
||||
}
|
||||
|
||||
|
@ -183,6 +211,8 @@ static const DuckyCmd ducky_commands[] = {
|
|||
{"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 "BadUsb"
|
||||
|
|
|
@ -7,6 +7,7 @@ extern "C" {
|
|||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include "ducky_script.h"
|
||||
#include "bad_usb_hid.h"
|
||||
|
||||
#define SCRIPT_STATE_ERROR (-1)
|
||||
#define SCRIPT_STATE_END (-2)
|
||||
|
@ -19,6 +20,8 @@ extern "C" {
|
|||
|
||||
struct BadUsbScript {
|
||||
FuriHalUsbHidConfig hid_cfg;
|
||||
const BadUsbHidApi* hid;
|
||||
void* hid_inst;
|
||||
FuriThread* thread;
|
||||
BadUsbState st;
|
||||
|
||||
|
@ -50,15 +53,17 @@ 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(void);
|
||||
void ducky_numlock_on(BadUsbScript* bad_usb);
|
||||
|
||||
bool ducky_numpad_press(const char num);
|
||||
bool ducky_numpad_press(BadUsbScript* bad_usb, const char num);
|
||||
|
||||
bool ducky_altchar(const char* charcode);
|
||||
bool ducky_altchar(BadUsbScript* bad_usb, const char* charcode);
|
||||
|
||||
bool ducky_altstring(const char* param);
|
||||
bool ducky_altstring(BadUsbScript* bad_usb, const char* param);
|
||||
|
||||
bool ducky_string(BadUsbScript* bad_usb, const char* param);
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#include <furi_hal.h>
|
||||
#include <furi_hal_usb_hid.h>
|
||||
#include "ducky_script_i.h"
|
||||
|
||||
typedef struct {
|
||||
|
@ -78,6 +77,37 @@ static const DuckyKey ducky_keys[] = {
|
|||
{"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);
|
||||
|
@ -89,3 +119,15 @@ uint16_t ducky_get_keycode_by_name(const char* param) {
|
|||
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -1,29 +1,62 @@
|
|||
#include "../bad_usb_app_i.h"
|
||||
#include "furi_hal_power.h"
|
||||
#include "furi_hal_usb.h"
|
||||
|
||||
enum SubmenuIndex {
|
||||
SubmenuIndexKeyboardLayout,
|
||||
ConfigIndexKeyboardLayout,
|
||||
ConfigIndexInterface,
|
||||
ConfigIndexBleUnpair,
|
||||
};
|
||||
|
||||
void bad_usb_scene_config_submenu_callback(void* context, uint32_t index) {
|
||||
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;
|
||||
Submenu* submenu = bad_usb->submenu;
|
||||
VariableItemList* var_item_list = bad_usb->var_item_list;
|
||||
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Keyboard Layout (global)",
|
||||
SubmenuIndexKeyboardLayout,
|
||||
bad_usb_scene_config_submenu_callback,
|
||||
bad_usb);
|
||||
|
||||
submenu_set_selected_item(
|
||||
submenu, scene_manager_get_scene_state(bad_usb->scene_manager, BadUsbSceneConfig));
|
||||
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);
|
||||
}
|
||||
|
@ -33,10 +66,13 @@ bool bad_usb_scene_config_on_event(void* context, SceneManagerEvent event) {
|
|||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
scene_manager_set_scene_state(bad_usb->scene_manager, BadUsbSceneConfig, event.event);
|
||||
consumed = true;
|
||||
if(event.event == SubmenuIndexKeyboardLayout) {
|
||||
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");
|
||||
}
|
||||
|
@ -47,7 +83,7 @@ bool bad_usb_scene_config_on_event(void* context, SceneManagerEvent event) {
|
|||
|
||||
void bad_usb_scene_config_on_exit(void* context) {
|
||||
BadUsbApp* bad_usb = context;
|
||||
Submenu* submenu = bad_usb->submenu;
|
||||
VariableItemList* var_item_list = bad_usb->var_item_list;
|
||||
|
||||
submenu_reset(submenu);
|
||||
variable_item_list_reset(var_item_list);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
#include "../bad_usb_app_i.h"
|
||||
#include "furi_hal_power.h"
|
||||
#include "furi_hal_usb.h"
|
||||
#include <storage/storage.h>
|
||||
|
||||
static bool bad_usb_layout_select(BadUsbApp* bad_usb) {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
#include "../bad_usb_app_i.h"
|
||||
#include <furi_hal_power.h>
|
||||
#include <furi_hal_usb.h>
|
||||
#include <storage/storage.h>
|
||||
|
||||
static bool bad_usb_file_select(BadUsbApp* bad_usb) {
|
||||
|
@ -28,9 +27,6 @@ void bad_usb_scene_file_select_on_enter(void* context) {
|
|||
}
|
||||
|
||||
if(bad_usb_file_select(bad_usb)) {
|
||||
bad_usb->bad_usb_script = bad_usb_script_open(bad_usb->file_path);
|
||||
bad_usb_script_set_keyboard_layout(bad_usb->bad_usb_script, bad_usb->keyboard_layout);
|
||||
|
||||
scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneWork);
|
||||
} else {
|
||||
view_dispatcher_stop(bad_usb->view_dispatcher);
|
||||
|
|
|
@ -16,7 +16,10 @@ bool bad_usb_scene_work_on_event(void* context, SceneManagerEvent event) {
|
|||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == InputKeyLeft) {
|
||||
if(bad_usb_is_idle_state(app->bad_usb_view)) {
|
||||
if(bad_usb_view_is_idle_state(app->bad_usb_view)) {
|
||||
bad_usb_script_close(app->bad_usb_script);
|
||||
app->bad_usb_script = NULL;
|
||||
|
||||
scene_manager_next_scene(app->scene_manager, BadUsbSceneConfig);
|
||||
}
|
||||
consumed = true;
|
||||
|
@ -28,7 +31,7 @@ bool bad_usb_scene_work_on_event(void* context, SceneManagerEvent event) {
|
|||
consumed = true;
|
||||
}
|
||||
} else if(event.type == SceneManagerEventTypeTick) {
|
||||
bad_usb_set_state(app->bad_usb_view, bad_usb_script_get_state(app->bad_usb_script));
|
||||
bad_usb_view_set_state(app->bad_usb_view, bad_usb_script_get_state(app->bad_usb_script));
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
@ -36,21 +39,24 @@ 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);
|
||||
bad_usb_script_set_keyboard_layout(app->bad_usb_script, app->keyboard_layout);
|
||||
|
||||
FuriString* file_name;
|
||||
file_name = furi_string_alloc();
|
||||
path_extract_filename(app->file_path, file_name, true);
|
||||
bad_usb_set_file_name(app->bad_usb_view, furi_string_get_cstr(file_name));
|
||||
bad_usb_view_set_file_name(app->bad_usb_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_usb_set_layout(app->bad_usb_view, furi_string_get_cstr(layout));
|
||||
bad_usb_view_set_layout(app->bad_usb_view, furi_string_get_cstr(layout));
|
||||
furi_string_free(layout);
|
||||
|
||||
bad_usb_set_state(app->bad_usb_view, bad_usb_script_get_state(app->bad_usb_script));
|
||||
bad_usb_view_set_state(app->bad_usb_view, bad_usb_script_get_state(app->bad_usb_script));
|
||||
|
||||
bad_usb_set_button_callback(app->bad_usb_view, bad_usb_scene_work_button_callback, app);
|
||||
bad_usb_view_set_button_callback(app->bad_usb_view, bad_usb_scene_work_button_callback, app);
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, BadUsbAppViewWork);
|
||||
}
|
||||
|
||||
|
|
|
@ -66,7 +66,7 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) {
|
|||
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 USB");
|
||||
canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "to device");
|
||||
} else if(state == BadUsbStateWillRun) {
|
||||
canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18);
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
|
@ -193,7 +193,7 @@ static bool bad_usb_input_callback(InputEvent* event, void* context) {
|
|||
return consumed;
|
||||
}
|
||||
|
||||
BadUsb* bad_usb_alloc(void) {
|
||||
BadUsb* bad_usb_view_alloc(void) {
|
||||
BadUsb* bad_usb = malloc(sizeof(BadUsb));
|
||||
|
||||
bad_usb->view = view_alloc();
|
||||
|
@ -205,18 +205,21 @@ BadUsb* bad_usb_alloc(void) {
|
|||
return bad_usb;
|
||||
}
|
||||
|
||||
void bad_usb_free(BadUsb* bad_usb) {
|
||||
void bad_usb_view_free(BadUsb* bad_usb) {
|
||||
furi_assert(bad_usb);
|
||||
view_free(bad_usb->view);
|
||||
free(bad_usb);
|
||||
}
|
||||
|
||||
View* bad_usb_get_view(BadUsb* bad_usb) {
|
||||
View* bad_usb_view_get_view(BadUsb* bad_usb) {
|
||||
furi_assert(bad_usb);
|
||||
return bad_usb->view;
|
||||
}
|
||||
|
||||
void bad_usb_set_button_callback(BadUsb* bad_usb, BadUsbButtonCallback callback, void* context) {
|
||||
void bad_usb_view_set_button_callback(
|
||||
BadUsb* bad_usb,
|
||||
BadUsbButtonCallback callback,
|
||||
void* context) {
|
||||
furi_assert(bad_usb);
|
||||
furi_assert(callback);
|
||||
with_view_model(
|
||||
|
@ -230,7 +233,7 @@ void bad_usb_set_button_callback(BadUsb* bad_usb, BadUsbButtonCallback callback,
|
|||
true);
|
||||
}
|
||||
|
||||
void bad_usb_set_file_name(BadUsb* bad_usb, const char* name) {
|
||||
void bad_usb_view_set_file_name(BadUsb* bad_usb, const char* name) {
|
||||
furi_assert(name);
|
||||
with_view_model(
|
||||
bad_usb->view,
|
||||
|
@ -239,7 +242,7 @@ void bad_usb_set_file_name(BadUsb* bad_usb, const char* name) {
|
|||
true);
|
||||
}
|
||||
|
||||
void bad_usb_set_layout(BadUsb* bad_usb, const char* layout) {
|
||||
void bad_usb_view_set_layout(BadUsb* bad_usb, const char* layout) {
|
||||
furi_assert(layout);
|
||||
with_view_model(
|
||||
bad_usb->view,
|
||||
|
@ -248,7 +251,7 @@ void bad_usb_set_layout(BadUsb* bad_usb, const char* layout) {
|
|||
true);
|
||||
}
|
||||
|
||||
void bad_usb_set_state(BadUsb* bad_usb, BadUsbState* st) {
|
||||
void bad_usb_view_set_state(BadUsb* bad_usb, BadUsbState* st) {
|
||||
furi_assert(st);
|
||||
with_view_model(
|
||||
bad_usb->view,
|
||||
|
@ -263,7 +266,7 @@ void bad_usb_set_state(BadUsb* bad_usb, BadUsbState* st) {
|
|||
true);
|
||||
}
|
||||
|
||||
bool bad_usb_is_idle_state(BadUsb* bad_usb) {
|
||||
bool bad_usb_view_is_idle_state(BadUsb* bad_usb) {
|
||||
bool is_idle = false;
|
||||
with_view_model(
|
||||
bad_usb->view,
|
||||
|
|
|
@ -6,18 +6,21 @@
|
|||
typedef struct BadUsb BadUsb;
|
||||
typedef void (*BadUsbButtonCallback)(InputKey key, void* context);
|
||||
|
||||
BadUsb* bad_usb_alloc(void);
|
||||
BadUsb* bad_usb_view_alloc(void);
|
||||
|
||||
void bad_usb_free(BadUsb* bad_usb);
|
||||
void bad_usb_view_free(BadUsb* bad_usb);
|
||||
|
||||
View* bad_usb_get_view(BadUsb* bad_usb);
|
||||
View* bad_usb_view_get_view(BadUsb* bad_usb);
|
||||
|
||||
void bad_usb_set_button_callback(BadUsb* bad_usb, BadUsbButtonCallback callback, void* context);
|
||||
void bad_usb_view_set_button_callback(
|
||||
BadUsb* bad_usb,
|
||||
BadUsbButtonCallback callback,
|
||||
void* context);
|
||||
|
||||
void bad_usb_set_file_name(BadUsb* bad_usb, const char* name);
|
||||
void bad_usb_view_set_file_name(BadUsb* bad_usb, const char* name);
|
||||
|
||||
void bad_usb_set_layout(BadUsb* bad_usb, const char* layout);
|
||||
void bad_usb_view_set_layout(BadUsb* bad_usb, const char* layout);
|
||||
|
||||
void bad_usb_set_state(BadUsb* bad_usb, BadUsbState* st);
|
||||
void bad_usb_view_set_state(BadUsb* bad_usb, BadUsbState* st);
|
||||
|
||||
bool bad_usb_is_idle_state(BadUsb* bad_usb);
|
||||
bool bad_usb_view_is_idle_state(BadUsb* bad_usb);
|
||||
|
|
|
@ -79,15 +79,7 @@ Up to 5 keys can be hold simultaneously.
|
|||
| HOLD | Special key or single character | Press and hold key until RELEASE command |
|
||||
| RELEASE | Special key or single character | Release key |
|
||||
|
||||
## Wait for button press
|
||||
|
||||
Will wait indefinitely for a button to be pressed
|
||||
| Command | Parameters | Notes |
|
||||
| --------------------- | ------------ | --------------------------------------------------------------------- |
|
||||
| WAIT_FOR_BUTTON_PRESS | None | Will wait for the user to press a button to continue script execution |
|
||||
|
||||
|
||||
### String
|
||||
## String
|
||||
|
||||
| Command | Parameters | Notes |
|
||||
| ------- | ----------- | ----------------- |
|
||||
|
@ -126,7 +118,54 @@ Send [SysRq command](https://en.wikipedia.org/wiki/Magic_SysRq_key)
|
|||
| ------- | ---------------- | ----- |
|
||||
| SYSRQ | Single character | |
|
||||
|
||||
### USB device ID
|
||||
## Media keys
|
||||
|
||||
Some Media/Consumer Control keys can be pressed with "MEDIA" command
|
||||
|
||||
| Command | Parameters | Notes |
|
||||
| ------- | ------------------------- | ----- |
|
||||
| MEDIA | Media key, see list below | |
|
||||
|
||||
| Key name | Notes |
|
||||
| ----------------- | ----------------------------- |
|
||||
| POWER | |
|
||||
| REBOOT | |
|
||||
| SLEEP | |
|
||||
| LOGOFF | |
|
||||
| EXIT | |
|
||||
| HOME | |
|
||||
| BACK | |
|
||||
| FORWARD | |
|
||||
| REFRESH | |
|
||||
| SNAPSHOT | Take photo in a camera app |
|
||||
| PLAY | |
|
||||
| PAUSE | |
|
||||
| PLAY_PAUSE | |
|
||||
| NEXT_TRACK | |
|
||||
| PREV_TRACK | |
|
||||
| STOP | |
|
||||
| EJECT | |
|
||||
| MUTE | |
|
||||
| VOLUME_UP | |
|
||||
| VOLUME_DOWN | |
|
||||
| FN | Fn/Globe key on Mac keyboard |
|
||||
| BRIGHT_UP | Increase display brightness |
|
||||
| BRIGHT_DOWN | Decrease display brightness |
|
||||
|
||||
## Fn/Globe key commands (Mac/iPad)
|
||||
|
||||
| Command | Parameters | Notes |
|
||||
| ------- | ------------------------------- | ----- |
|
||||
| GLOBE | Special key or single character | |
|
||||
|
||||
## Wait for button press
|
||||
|
||||
Will wait indefinitely for a button to be pressed
|
||||
| Command | Parameters | Notes |
|
||||
| --------------------- | ------------ | --------------------------------------------------------------------- |
|
||||
| WAIT_FOR_BUTTON_PRESS | None | Will wait for the user to press a button to continue script execution |
|
||||
|
||||
## USB device ID
|
||||
|
||||
You can set the custom ID of the Flipper USB HID device. ID command should be in the **first line** of script, it is executed before script run.
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
entry,status,name,type,params
|
||||
Version,+,60.1,,
|
||||
Version,+,60.2,,
|
||||
Header,+,applications/services/bt/bt_service/bt.h,,
|
||||
Header,+,applications/services/cli/cli.h,,
|
||||
Header,+,applications/services/cli/cli_vcp.h,,
|
||||
|
@ -1205,6 +1205,7 @@ Function,+,furi_hal_gpio_init_simple,void,"const GpioPin*, const GpioMode"
|
|||
Function,+,furi_hal_gpio_remove_int_callback,void,const GpioPin*
|
||||
Function,+,furi_hal_hid_consumer_key_press,_Bool,uint16_t
|
||||
Function,+,furi_hal_hid_consumer_key_release,_Bool,uint16_t
|
||||
Function,+,furi_hal_hid_consumer_key_release_all,_Bool,
|
||||
Function,+,furi_hal_hid_get_led_state,uint8_t,
|
||||
Function,+,furi_hal_hid_is_connected,_Bool,
|
||||
Function,+,furi_hal_hid_kb_press,_Bool,uint16_t
|
||||
|
|
|
|
@ -1,5 +1,5 @@
|
|||
entry,status,name,type,params
|
||||
Version,+,60.1,,
|
||||
Version,+,60.2,,
|
||||
Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,,
|
||||
Header,+,applications/services/bt/bt_service/bt.h,,
|
||||
Header,+,applications/services/cli/cli.h,,
|
||||
|
@ -1273,6 +1273,7 @@ Function,+,furi_hal_gpio_init_simple,void,"const GpioPin*, const GpioMode"
|
|||
Function,+,furi_hal_gpio_remove_int_callback,void,const GpioPin*
|
||||
Function,+,furi_hal_hid_consumer_key_press,_Bool,uint16_t
|
||||
Function,+,furi_hal_hid_consumer_key_release,_Bool,uint16_t
|
||||
Function,+,furi_hal_hid_consumer_key_release_all,_Bool,
|
||||
Function,+,furi_hal_hid_get_led_state,uint8_t,
|
||||
Function,+,furi_hal_hid_is_connected,_Bool,
|
||||
Function,+,furi_hal_hid_kb_press,_Bool,uint16_t
|
||||
|
|
|
|
@ -354,6 +354,13 @@ bool furi_hal_hid_consumer_key_release(uint16_t button) {
|
|||
return hid_send_report(ReportIdConsumer);
|
||||
}
|
||||
|
||||
bool furi_hal_hid_consumer_key_release_all(void) {
|
||||
for(uint8_t key_nb = 0; key_nb < HID_CONSUMER_MAX_KEYS; key_nb++) {
|
||||
hid_report.consumer.btn[key_nb] = 0;
|
||||
}
|
||||
return hid_send_report(ReportIdConsumer);
|
||||
}
|
||||
|
||||
static void* hid_set_string_descr(char* str) {
|
||||
furi_assert(str);
|
||||
|
||||
|
|
|
@ -14,6 +14,11 @@ extern "C" {
|
|||
/** Max number of simultaneously pressed keys (consumer control) */
|
||||
#define HID_CONSUMER_MAX_KEYS 2
|
||||
|
||||
/** OS-specific consumer keys, defined as "Reserved" in HID Usage Tables document */
|
||||
#define HID_CONSUMER_BRIGHTNESS_INCREMENT 0x006F
|
||||
#define HID_CONSUMER_BRIGHTNESS_DECREMENT 0x0070
|
||||
#define HID_CONSUMER_FN_GLOBE 0x029D
|
||||
|
||||
#define HID_KEYBOARD_NONE 0x00
|
||||
|
||||
/** HID keyboard modifier keys */
|
||||
|
@ -259,6 +264,11 @@ bool furi_hal_hid_consumer_key_press(uint16_t button);
|
|||
*/
|
||||
bool furi_hal_hid_consumer_key_release(uint16_t button);
|
||||
|
||||
/** Clear all pressed consumer keys and send HID report
|
||||
*
|
||||
*/
|
||||
bool furi_hal_hid_consumer_key_release_all(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
Loading…
Reference in a new issue