From e6642b332c1e71c00dfcdc5959358a274e840a2a Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Wed, 13 Oct 2021 19:38:24 +0300 Subject: [PATCH] [FL-1930] USB HID (#751) * [FL-1930] USB HID keyboard test * [FL-1930] HID mouse demo app * [FL-1930] BadUSB: RubberDucky script parser. BadUSB test app Co-authored-by: Aleksandr Kutuzov --- applications/applications.c | 10 + applications/applications.mk | 17 +- applications/debug_tools/bad_usb.c | 364 ++++++++++++++++++ applications/debug_tools/usb_mouse.c | 121 ++++++ applications/debug_tools/usb_test.c | 1 + .../targets/f6/furi-hal/furi-hal-usb-hid.c | 211 +++++++--- firmware/targets/f6/furi-hal/furi-hal-usb.c | 4 + .../targets/f7/furi-hal/furi-hal-usb-hid.c | 211 +++++++--- firmware/targets/f7/furi-hal/furi-hal-usb.c | 4 + .../furi-hal-include/furi-hal-usb-hid.h | 309 +++++++++++++++ .../targets/furi-hal-include/furi-hal-usb.h | 17 + firmware/targets/furi-hal-include/furi-hal.h | 1 + 12 files changed, 1167 insertions(+), 103 deletions(-) create mode 100644 applications/debug_tools/bad_usb.c create mode 100644 applications/debug_tools/usb_mouse.c create mode 100644 firmware/targets/furi-hal-include/furi-hal-usb-hid.h diff --git a/applications/applications.c b/applications/applications.c index 761dd8fc..edb6f466 100644 --- a/applications/applications.c +++ b/applications/applications.c @@ -35,6 +35,8 @@ extern int32_t subghz_app(void* p); extern int32_t vibro_test_app(void* p); extern int32_t bt_debug_app(void* p); extern int32_t usb_test_app(void* p); +extern int32_t usb_mouse_app(void* p); +extern int32_t bad_usb_app(void* p); // Plugins extern int32_t music_player_app(void* p); @@ -225,6 +227,14 @@ const FlipperApplication FLIPPER_DEBUG_APPS[] = { {.app = usb_test_app, .name = "USB Test", .stack_size = 1024, .icon = &A_Plugins_14}, #endif +#ifdef APP_USB_MOUSE + {.app = usb_mouse_app, .name = "USB Mouse demo", .stack_size = 1024, .icon = &A_Plugins_14}, +#endif + +#ifdef APP_BAD_USB + {.app = bad_usb_app, .name = "Bad USB test", .stack_size = 2048, .icon = &A_Plugins_14}, +#endif + #ifdef APP_IRDA_MONITOR {.app = irda_monitor_app, .name = "Irda Monitor", .stack_size = 1024, .icon = &A_Plugins_14}, #endif diff --git a/applications/applications.mk b/applications/applications.mk index 4e9f6d56..ade6491c 100644 --- a/applications/applications.mk +++ b/applications/applications.mk @@ -44,6 +44,8 @@ APP_KEYPAD_TEST = 1 APP_SD_TEST = 1 APP_VIBRO_DEMO = 1 APP_USB_TEST = 1 +APP_USB_MOUSE = 1 +APP_BAD_USB = 1 endif @@ -127,8 +129,21 @@ ifeq ($(APP_USB_TEST), 1) CFLAGS += -DAPP_USB_TEST SRV_INPUT = 1 SRV_GUI = 1 -endif +endif +APP_USB_MOUSE ?= 0 +ifeq ($(APP_USB_MOUSE), 1) +CFLAGS += -DAPP_USB_MOUSE +SRV_INPUT = 1 +SRV_GUI = 1 +endif + +APP_BAD_USB ?= 0 +ifeq ($(APP_BAD_USB), 1) +CFLAGS += -DAPP_BAD_USB +SRV_INPUT = 1 +SRV_GUI = 1 +endif APP_KEYPAD_TEST ?= 0 ifeq ($(APP_KEYPAD_TEST), 1) diff --git a/applications/debug_tools/bad_usb.c b/applications/debug_tools/bad_usb.c new file mode 100644 index 00000000..1bd35d59 --- /dev/null +++ b/applications/debug_tools/bad_usb.c @@ -0,0 +1,364 @@ +#include +#include +#include +#include +#include +#include +#include + +typedef enum { + EventTypeInput, + EventTypeWorkerState, +} EventType; + +typedef enum { + WorkerStateDone, + WorkerStateNoFile, + WorkerStateScriptError, + WorkerStateDisconnected, +} WorkerState; + +typedef enum { + AppStateWait, + AppStateRunning, + AppStateError, + AppStateExit, +} AppState; + +typedef enum { + WorkerCmdStart = (1 << 0), + WorkerCmdStop = (1 << 1), +} WorkerCommandFlags; + +// Event message from worker +typedef struct { + WorkerState state; + uint16_t line; +} BadUsbWorkerState; + +typedef struct { + union { + InputEvent input; + BadUsbWorkerState worker; + }; + EventType type; +} BadUsbEvent; + +typedef struct { + uint32_t defdelay; + char msg_text[32]; + osThreadAttr_t thread_attr; + osThreadId_t thread; + osMessageQueueId_t event_queue; +} BadUsbParams; + +typedef struct { + char* name; + uint16_t keycode; +} DuckyKey; + +static const DuckyKey ducky_keys[] = { + {"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", KEY_DOWN_ARROW}, + {"DOWN", KEY_DOWN_ARROW}, + {"LEFTARROW", KEY_LEFT_ARROW}, + {"LEFT", KEY_LEFT_ARROW}, + {"RIGHTARROW", KEY_RIGHT_ARROW}, + {"RIGHT", KEY_RIGHT_ARROW}, + {"UPARROW", KEY_UP_ARROW}, + {"UP", KEY_UP_ARROW}, + + {"ENTER", KEY_ENTER}, + {"BREAK", KEY_PAUSE}, + {"PAUSE", KEY_PAUSE}, + {"CAPSLOCK", KEY_CAPS_LOCK}, + {"DELETE", KEY_DELETE}, + {"BACKSPACE", KEY_BACKSPACE}, + {"END", KEY_END}, + {"ESC", KEY_ESC}, + {"ESCAPE", KEY_ESC}, + {"HOME", KEY_HOME}, + {"INSERT", KEY_INSERT}, + {"NUMLOCK", KEY_NUM_LOCK}, + {"PAGEUP", KEY_PAGE_UP}, + {"PAGEDOWN", KEY_PAGE_DOWN}, + {"PRINTSCREEN", KEY_PRINT}, + {"SCROLLOCK", KEY_SCROLL_LOCK}, + {"SPACE", KEY_SPACE}, + {"TAB", KEY_TAB}, + {"MENU", KEY_APPLICATION}, + {"APP", KEY_APPLICATION}, +}; + +static const char ducky_cmd_comment[] = {"REM"}; +static const char ducky_cmd_delay[] = {"DELAY"}; +static const char ducky_cmd_string[] = {"STRING"}; +static const char ducky_cmd_defdelay_1[] = {"DEFAULT_DELAY"}; +static const char ducky_cmd_defdelay_2[] = {"DEFAULTDELAY"}; + +static bool ducky_get_delay_val(char* param, uint32_t* val) { + uint32_t delay_val = 0; + if(sscanf(param, "%lu", &delay_val) == 1) { + *val = delay_val; + return true; + } + return false; +} + +static bool ducky_string(char* param) { + uint32_t i = 0; + while(param[i] != '\0') { + furi_hal_hid_kb_press(HID_ASCII_TO_KEY(param[i])); + furi_hal_hid_kb_release(HID_ASCII_TO_KEY(param[i])); + i++; + } + return true; +} + +static uint16_t ducky_get_keycode(char* param, bool accept_chars) { + for(uint8_t i = 0; i < (sizeof(ducky_keys) / sizeof(ducky_keys[0])); i++) { + if(strncmp(param, ducky_keys[i].name, strlen(ducky_keys[i].name)) == 0) + return ducky_keys[i].keycode; + } + if((accept_chars) && (strlen(param) > 0)) { + return (HID_ASCII_TO_KEY(param[0]) & 0xFF); + } + return 0; +} + +static bool ducky_parse_line(string_t line, BadUsbParams* app) { + //uint32_t line_len = string_size(line); + char* line_t = (char*)string_get_cstr(line); + bool state = false; + + // General commands + if(strncmp(line_t, ducky_cmd_comment, strlen(ducky_cmd_comment)) == 0) { + // REM - comment line + return true; + } else if(strncmp(line_t, ducky_cmd_delay, strlen(ducky_cmd_delay)) == 0) { + // DELAY + line_t = &line_t[args_get_first_word_length(line) + 1]; + uint32_t delay_val = 0; + state = ducky_get_delay_val(line_t, &delay_val); + if((state) && (delay_val > 0)) { + // Using ThreadFlagsWait as delay function allows exiting task on WorkerCmdStop command + if(osThreadFlagsWait(WorkerCmdStop, osFlagsWaitAny | osFlagsNoClear, delay_val) == + WorkerCmdStop) + return true; + } + return state; + } else if( + (strncmp(line_t, ducky_cmd_defdelay_1, strlen(ducky_cmd_defdelay_1)) == 0) || + (strncmp(line_t, ducky_cmd_defdelay_2, strlen(ducky_cmd_defdelay_2)) == 0)) { + // DEFAULT_DELAY + line_t = &line_t[args_get_first_word_length(line) + 1]; + return ducky_get_delay_val(line_t, &app->defdelay); + } else if(strncmp(line_t, ducky_cmd_string, strlen(ducky_cmd_string)) == 0) { + // STRING + if(app->defdelay > 0) { + if(osThreadFlagsWait(WorkerCmdStop, osFlagsWaitAny | osFlagsNoClear, app->defdelay) == + WorkerCmdStop) + return true; + } + line_t = &line_t[args_get_first_word_length(line) + 1]; + return ducky_string(line_t); + } else { + // Special keys + modifiers + uint16_t key = ducky_get_keycode(line_t, false); + if(key == KEY_NONE) return false; + if((key & 0xFF00) != 0) { + // It's a modifier key + line_t = &line_t[args_get_first_word_length(line) + 1]; + key |= ducky_get_keycode(line_t, true); + } + if(app->defdelay > 0) { + if(osThreadFlagsWait(WorkerCmdStop, osFlagsWaitAny | osFlagsNoClear, app->defdelay) == + WorkerCmdStop) + return true; + } + furi_hal_hid_kb_press(key); + furi_hal_hid_kb_release(key); + return true; + } + return false; +} + +static void badusb_worker(void* context) { + BadUsbParams* app = context; + FURI_LOG_I("BadUSB worker", "Init"); + File* script_file = storage_file_alloc(furi_record_open("storage")); + BadUsbEvent evt; + string_t line; + uint32_t line_cnt = 0; + string_init(line); + if(storage_file_open(script_file, "/ext/badusb.txt", FSAM_READ, FSOM_OPEN_EXISTING)) { + char buffer[16]; + uint16_t ret; + uint32_t flags = + osThreadFlagsWait(WorkerCmdStart | WorkerCmdStop, osFlagsWaitAny, osWaitForever); + if(flags & WorkerCmdStart) { + FURI_LOG_I("BadUSB worker", "Start"); + do { + ret = storage_file_read(script_file, buffer, 16); + for(uint16_t i = 0; i < ret; i++) { + if(buffer[i] == '\n' && string_size(line) > 0) { + line_cnt++; + if(ducky_parse_line(line, app) == false) { + ret = 0; + FURI_LOG_E("BadUSB worker", "Unknown command at line %lu", line_cnt); + evt.type = EventTypeWorkerState; + evt.worker.state = WorkerStateScriptError; + evt.worker.line = line_cnt; + osMessageQueuePut(app->event_queue, &evt, 0, osWaitForever); + break; + } + flags = osThreadFlagsGet(); + if(flags == WorkerCmdStop) { + ret = 0; + break; + } + string_clean(line); + } else { + string_push_back(line, buffer[i]); + } + } + } while(ret > 0); + } + } else { + FURI_LOG_E("BadUSB worker", "Script file open error"); + evt.type = EventTypeWorkerState; + evt.worker.state = WorkerStateNoFile; + osMessageQueuePut(app->event_queue, &evt, 0, osWaitForever); + } + string_clean(line); + string_clear(line); + + furi_hal_hid_kb_release_all(); + storage_file_close(script_file); + storage_file_free(script_file); + + FURI_LOG_I("BadUSB worker", "End"); + evt.type = EventTypeWorkerState; + evt.worker.state = WorkerStateDone; + osMessageQueuePut(app->event_queue, &evt, 0, osWaitForever); + + osThreadExit(); +} + +static void bad_usb_render_callback(Canvas* canvas, void* ctx) { + BadUsbParams* app = (BadUsbParams*)ctx; + + canvas_clear(canvas); + + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 0, 10, "Bad USB test"); + + if(strlen(app->msg_text) > 0) { + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 0, 62, app->msg_text); + } +} + +static void bad_usb_input_callback(InputEvent* input_event, void* ctx) { + osMessageQueueId_t event_queue = ctx; + + BadUsbEvent event; + event.type = EventTypeInput; + event.input = *input_event; + osMessageQueuePut(event_queue, &event, 0, osWaitForever); +} + +int32_t bad_usb_app(void* p) { + BadUsbParams* app = furi_alloc(sizeof(BadUsbParams)); + app->event_queue = osMessageQueueNew(8, sizeof(BadUsbEvent), NULL); + furi_check(app->event_queue); + ViewPort* view_port = view_port_alloc(); + + UsbMode usb_mode_prev = furi_hal_usb_get_config(); + furi_hal_usb_set_config(UsbModeHid); + + view_port_draw_callback_set(view_port, bad_usb_render_callback, app); + view_port_input_callback_set(view_port, bad_usb_input_callback, app->event_queue); + + // Open GUI and register view_port + Gui* gui = furi_record_open("gui"); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + app->thread = NULL; + app->thread_attr.name = "bad_usb_worker"; + app->thread_attr.stack_size = 2048; + app->thread = osThreadNew(badusb_worker, app, &app->thread_attr); + bool worker_running = true; + AppState app_state = AppStateWait; + snprintf(app->msg_text, sizeof(app->msg_text), "Press [OK] to start"); + view_port_update(view_port); + + BadUsbEvent event; + while(1) { + osStatus_t event_status = osMessageQueueGet(app->event_queue, &event, NULL, osWaitForever); + + if(event_status == osOK) { + if(event.type == EventTypeInput) { + if(event.input.type == InputTypeShort && event.input.key == InputKeyBack) { + if(worker_running) { + osThreadFlagsSet(app->thread, WorkerCmdStop); + app_state = AppStateExit; + } else + break; + } + + if(event.input.type == InputTypeShort && event.input.key == InputKeyOk) { + if(worker_running) { + app_state = AppStateRunning; + osThreadFlagsSet(app->thread, WorkerCmdStart); + snprintf(app->msg_text, sizeof(app->msg_text), "Running..."); + view_port_update(view_port); + } + } + } else if(event.type == EventTypeWorkerState) { + FURI_LOG_I("BadUSB app", "ev: %d", event.worker.state); + if(event.worker.state == WorkerStateDone) { + worker_running = false; + if(app_state == AppStateExit) + break; + else if(app_state == AppStateRunning) { + //done + app->thread = osThreadNew(badusb_worker, app, &app->thread_attr); + worker_running = true; + app_state = AppStateWait; + snprintf(app->msg_text, sizeof(app->msg_text), "Press [OK] to start"); + view_port_update(view_port); + } + } else if(event.worker.state == WorkerStateNoFile) { + app_state = AppStateError; + snprintf(app->msg_text, sizeof(app->msg_text), "File not found!"); + view_port_update(view_port); + } else if(event.worker.state == WorkerStateScriptError) { + app_state = AppStateError; + snprintf( + app->msg_text, + sizeof(app->msg_text), + "Error at line %u", + event.worker.line); + view_port_update(view_port); + } + } + } + } + furi_hal_usb_set_config(usb_mode_prev); + + // remove & free all stuff created by app + gui_remove_view_port(gui, view_port); + view_port_free(view_port); + + osMessageQueueDelete(app->event_queue); + free(app); + + return 0; +} diff --git a/applications/debug_tools/usb_mouse.c b/applications/debug_tools/usb_mouse.c new file mode 100644 index 00000000..a9b8be6e --- /dev/null +++ b/applications/debug_tools/usb_mouse.c @@ -0,0 +1,121 @@ +#include +#include +#include +#include + +#define MOUSE_MOVE_SHORT 5 +#define MOUSE_MOVE_LONG 20 + +typedef enum { + EventTypeInput, +} EventType; + +typedef struct { + union { + InputEvent input; + }; + EventType type; +} UsbMouseEvent; + +static void usb_mouse_render_callback(Canvas* canvas, void* ctx) { + canvas_clear(canvas); + + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 0, 10, "USB Mouse demo"); + + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 0, 63, "Hold [back] to exit"); +} + +static void usb_mouse_input_callback(InputEvent* input_event, void* ctx) { + osMessageQueueId_t event_queue = ctx; + + UsbMouseEvent event; + event.type = EventTypeInput; + event.input = *input_event; + osMessageQueuePut(event_queue, &event, 0, osWaitForever); +} + +int32_t usb_mouse_app(void* p) { + osMessageQueueId_t event_queue = osMessageQueueNew(8, sizeof(UsbMouseEvent), NULL); + furi_check(event_queue); + ViewPort* view_port = view_port_alloc(); + + UsbMode usb_mode_prev = furi_hal_usb_get_config(); + furi_hal_usb_set_config(UsbModeHid); + + view_port_draw_callback_set(view_port, usb_mouse_render_callback, NULL); + view_port_input_callback_set(view_port, usb_mouse_input_callback, event_queue); + + // Open GUI and register view_port + Gui* gui = furi_record_open("gui"); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + UsbMouseEvent event; + while(1) { + osStatus_t event_status = osMessageQueueGet(event_queue, &event, NULL, osWaitForever); + + if(event_status == osOK) { + if(event.type == EventTypeInput) { + if(event.input.type == InputTypeLong && event.input.key == InputKeyBack) { + break; + } + + if(event.input.type == InputTypeShort && event.input.key == InputKeyBack) { + furi_hal_hid_mouse_press(HID_MOUSE_BTN_RIGHT); + furi_hal_hid_mouse_release(HID_MOUSE_BTN_RIGHT); + } + + if(event.input.key == InputKeyOk) { + if(event.input.type == InputTypePress) { + furi_hal_hid_mouse_press(HID_MOUSE_BTN_LEFT); + } else if(event.input.type == InputTypeRelease) { + furi_hal_hid_mouse_release(HID_MOUSE_BTN_LEFT); + } + } + + if(event.input.key == InputKeyRight) { + if(event.input.type == InputTypePress) { + furi_hal_hid_mouse_move(MOUSE_MOVE_SHORT, 0); + } else if(event.input.type == InputTypeRepeat) { + furi_hal_hid_mouse_move(MOUSE_MOVE_LONG, 0); + } + } + + if(event.input.key == InputKeyLeft) { + if(event.input.type == InputTypePress) { + furi_hal_hid_mouse_move(-MOUSE_MOVE_SHORT, 0); + } else if(event.input.type == InputTypeRepeat) { + furi_hal_hid_mouse_move(-MOUSE_MOVE_LONG, 0); + } + } + + if(event.input.key == InputKeyDown) { + if(event.input.type == InputTypePress) { + furi_hal_hid_mouse_move(0, MOUSE_MOVE_SHORT); + } else if(event.input.type == InputTypeRepeat) { + furi_hal_hid_mouse_move(0, MOUSE_MOVE_LONG); + } + } + + if(event.input.key == InputKeyUp) { + if(event.input.type == InputTypePress) { + furi_hal_hid_mouse_move(0, -MOUSE_MOVE_SHORT); + } else if(event.input.type == InputTypeRepeat) { + furi_hal_hid_mouse_move(0, -MOUSE_MOVE_LONG); + } + } + } + } + view_port_update(view_port); + } + + furi_hal_usb_set_config(usb_mode_prev); + + // remove & free all stuff created by app + gui_remove_view_port(gui, view_port); + view_port_free(view_port); + osMessageQueueDelete(event_queue); + + return 0; +} diff --git a/applications/debug_tools/usb_test.c b/applications/debug_tools/usb_test.c index e921d8b3..f1677cae 100644 --- a/applications/debug_tools/usb_test.c +++ b/applications/debug_tools/usb_test.c @@ -4,6 +4,7 @@ #include #include #include +#include typedef struct { Gui* gui; diff --git a/firmware/targets/f6/furi-hal/furi-hal-usb-hid.c b/firmware/targets/f6/furi-hal/furi-hal-usb-hid.c index 38423610..d390c7b5 100644 --- a/firmware/targets/f6/furi-hal/furi-hal-usb-hid.c +++ b/firmware/targets/f6/furi-hal/furi-hal-usb-hid.c @@ -6,10 +6,13 @@ #include "usb_hid.h" #include "hid_usage_desktop.h" #include "hid_usage_button.h" +#include "hid_usage_keyboard.h" #define HID_RIN_EP 0x81 #define HID_RIN_SZ 0x10 +#define HID_KB_MAX_KEYS 6 + struct HidIadDescriptor { struct usb_iad_descriptor hid_iad; struct usb_interface_descriptor hid; @@ -22,31 +25,63 @@ struct HidConfigDescriptor { struct HidIadDescriptor iad_0; } __attribute__((packed)); -/* HID mouse report desscriptor. 2 axis 5 buttons */ +enum HidReportId { + ReportIdKeyboard = 1, + ReportIdMouse = 2, +}; + +/* HID report: keyboard+mouse */ static const uint8_t hid_report_desc[] = { + HID_USAGE_PAGE(HID_PAGE_DESKTOP), + HID_USAGE(HID_DESKTOP_KEYBOARD), + HID_COLLECTION(HID_APPLICATION_COLLECTION), + HID_REPORT_ID(ReportIdKeyboard), + HID_USAGE_PAGE(HID_DESKTOP_KEYPAD), + HID_USAGE_MINIMUM(HID_KEYBOARD_L_CTRL), + HID_USAGE_MAXIMUM(HID_KEYBOARD_R_GUI), + HID_LOGICAL_MINIMUM(0), + HID_LOGICAL_MAXIMUM(1), + HID_REPORT_SIZE(1), + HID_REPORT_COUNT(8), + HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), + HID_REPORT_COUNT(1), + HID_REPORT_SIZE(8), + HID_INPUT(HID_IOF_CONSTANT | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), + HID_REPORT_COUNT(6), + HID_REPORT_SIZE(8), + HID_LOGICAL_MINIMUM(0), + HID_LOGICAL_MAXIMUM(101), + HID_USAGE_PAGE(HID_DESKTOP_KEYPAD), + HID_USAGE_MINIMUM(0), + HID_USAGE_MAXIMUM(101), + HID_INPUT(HID_IOF_DATA | HID_IOF_ARRAY | HID_IOF_ABSOLUTE), + HID_END_COLLECTION, HID_USAGE_PAGE(HID_PAGE_DESKTOP), HID_USAGE(HID_DESKTOP_MOUSE), HID_COLLECTION(HID_APPLICATION_COLLECTION), HID_USAGE(HID_DESKTOP_POINTER), HID_COLLECTION(HID_PHYSICAL_COLLECTION), + HID_REPORT_ID(ReportIdMouse), + HID_USAGE_PAGE(HID_PAGE_BUTTON), + HID_USAGE_MINIMUM(1), + HID_USAGE_MAXIMUM(3), + HID_LOGICAL_MINIMUM(0), + HID_LOGICAL_MAXIMUM(1), + HID_REPORT_COUNT(3), + HID_REPORT_SIZE(1), + HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), + HID_REPORT_SIZE(1), + HID_REPORT_COUNT(5), + HID_INPUT(HID_IOF_CONSTANT | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), + HID_USAGE_PAGE(HID_PAGE_DESKTOP), HID_USAGE(HID_DESKTOP_X), HID_USAGE(HID_DESKTOP_Y), + HID_USAGE(HID_DESKTOP_WHEEL), HID_LOGICAL_MINIMUM(-127), HID_LOGICAL_MAXIMUM(127), HID_REPORT_SIZE(8), - HID_REPORT_COUNT(2), - HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_RELATIVE ), - HID_USAGE_PAGE(HID_PAGE_BUTTON), - HID_USAGE_MINIMUM(1), - HID_USAGE_MAXIMUM(5), - HID_LOGICAL_MINIMUM(0), - HID_LOGICAL_MAXIMUM(1), - HID_REPORT_SIZE(1), - HID_REPORT_COUNT(5), - HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE ), - HID_REPORT_SIZE(1), HID_REPORT_COUNT(3), - HID_INPUT(HID_IOF_CONSTANT), + HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_RELATIVE), HID_END_COLLECTION, HID_END_COLLECTION, }; @@ -122,25 +157,102 @@ static const struct HidConfigDescriptor hid_cfg_desc = { .bEndpointAddress = HID_RIN_EP, .bmAttributes = USB_EPTYPE_INTERRUPT, .wMaxPacketSize = HID_RIN_SZ, - .bInterval = 50, + .bInterval = 10, }, }, }; -static struct { - int8_t x; - int8_t y; - uint8_t buttons; -} __attribute__((packed)) hid_report_data; +struct HidReportMouse { + uint8_t report_id; + uint8_t btn; + int8_t x; + int8_t y; + int8_t wheel; +} __attribute__((packed)); + +struct HidReportKB { + uint8_t report_id; + uint8_t mods; + uint8_t reserved; + uint8_t btn[HID_KB_MAX_KEYS]; +} __attribute__((packed)); + +static struct HidReport { + struct HidReportKB keyboard; + struct HidReportMouse mouse; +} __attribute__((packed)) hid_report; static void hid_init(usbd_device* dev, struct UsbInterface* intf); static void hid_deinit(usbd_device *dev); static void hid_on_wakeup(usbd_device *dev); static void hid_on_suspend(usbd_device *dev); +static bool hid_send_report(uint8_t report_id); static usbd_respond hid_ep_config (usbd_device *dev, uint8_t cfg); static usbd_respond hid_control (usbd_device *dev, usbd_ctlreq *req, usbd_rqc_callback *callback); static usbd_device* usb_dev; +static osSemaphoreId_t hid_semaphore = NULL; +static bool hid_connected = false; + +bool furi_hal_hid_is_connected() { + return hid_connected; +} + +bool furi_hal_hid_kb_press(uint16_t button) { + for (uint8_t key_nb = 0; key_nb < HID_KB_MAX_KEYS; key_nb++) { + if (hid_report.keyboard.btn[key_nb] == 0) { + hid_report.keyboard.btn[key_nb] = button & 0xFF; + break; + } + } + hid_report.keyboard.mods |= (button >> 8); + return hid_send_report(ReportIdKeyboard); +} + +bool furi_hal_hid_kb_release(uint16_t button) { + for (uint8_t key_nb = 0; key_nb < HID_KB_MAX_KEYS; key_nb++) { + if (hid_report.keyboard.btn[key_nb] == (button & 0xFF)) { + hid_report.keyboard.btn[key_nb] = 0; + break; + } + } + hid_report.keyboard.mods &= ~(button >> 8); + return hid_send_report(ReportIdKeyboard); +} + +bool furi_hal_hid_kb_release_all() { + for (uint8_t key_nb = 0; key_nb < HID_KB_MAX_KEYS; key_nb++) { + hid_report.keyboard.btn[key_nb] = 0; + } + hid_report.keyboard.mods = 0; + return hid_send_report(ReportIdKeyboard); +} + +bool furi_hal_hid_mouse_move(int8_t dx, int8_t dy) { + hid_report.mouse.x = dx; + hid_report.mouse.y = dy; + bool state = hid_send_report(ReportIdMouse); + hid_report.mouse.x = 0; + hid_report.mouse.y = 0; + return state; +} + +bool furi_hal_hid_mouse_press(uint8_t button) { + hid_report.mouse.btn |= button; + return hid_send_report(ReportIdMouse); +} + +bool furi_hal_hid_mouse_release(uint8_t button) { + hid_report.mouse.btn &= ~button; + return hid_send_report(ReportIdMouse); +} + +bool furi_hal_hid_mouse_scroll(int8_t delta) { + hid_report.mouse.wheel = delta; + bool state = hid_send_report(ReportIdMouse); + hid_report.mouse.wheel = 0; + return state; +} struct UsbInterface usb_hid = { .init = hid_init, @@ -158,7 +270,11 @@ struct UsbInterface usb_hid = { }; static void hid_init(usbd_device* dev, struct UsbInterface* intf) { + if (hid_semaphore == NULL) + hid_semaphore = osSemaphoreNew(1, 1, NULL); usb_dev = dev; + hid_report.keyboard.report_id = ReportIdKeyboard; + hid_report.mouse.report_id = ReportIdMouse; usbd_reg_config(dev, hid_ep_config); usbd_reg_control(dev, hid_control); @@ -172,41 +288,34 @@ static void hid_deinit(usbd_device *dev) { } static void hid_on_wakeup(usbd_device *dev) { + hid_connected = true; } static void hid_on_suspend(usbd_device *dev) { + if (hid_connected == true) { + hid_connected = false; + osSemaphoreRelease(hid_semaphore); + } } -/* HID mouse IN endpoint callback */ -static void hid_mouse_move(usbd_device *dev, uint8_t event, uint8_t ep) { - static uint8_t t = 0; - if (t < 0x10) { - hid_report_data.x = 1; - hid_report_data.y = 0; - } else if (t < 0x20) { - hid_report_data.x = 1; - hid_report_data.y = 1; - } else if (t < 0x30) { - hid_report_data.x = 0; - hid_report_data.y = 1; - } else if (t < 0x40) { - hid_report_data.x = -1; - hid_report_data.y = 1; - } else if (t < 0x50) { - hid_report_data.x = -1; - hid_report_data.y = 0; - } else if (t < 0x60) { - hid_report_data.x = -1; - hid_report_data.y = -1; - } else if (t < 0x70) { - hid_report_data.x = 0; - hid_report_data.y = -1; - } else { - hid_report_data.x = 1; - hid_report_data.y = -1; +static bool hid_send_report(uint8_t report_id) +{ + if ((hid_semaphore == NULL) || (hid_connected == false)) + return false; + + furi_check(osSemaphoreAcquire(hid_semaphore, osWaitForever) == osOK); + if (hid_connected == true) { + if (report_id == ReportIdKeyboard) + usbd_ep_write(usb_dev, HID_RIN_EP, &hid_report.keyboard, sizeof(hid_report.keyboard)); + else + usbd_ep_write(usb_dev, HID_RIN_EP, &hid_report.mouse, sizeof(hid_report.mouse)); + return true; } - t = (t + 1) & 0x7F; - usbd_ep_write(dev, ep, &hid_report_data, sizeof(hid_report_data)); + return false; +} + +static void hid_ep_callback(usbd_device *dev, uint8_t event, uint8_t ep) { + osSemaphoreRelease(hid_semaphore); } /* Configure endpoints */ @@ -220,7 +329,7 @@ static usbd_respond hid_ep_config (usbd_device *dev, uint8_t cfg) { case 1: /* configuring device */ usbd_ep_config(dev, HID_RIN_EP, USB_EPTYPE_INTERRUPT, HID_RIN_SZ); - usbd_reg_endpoint(dev, HID_RIN_EP, hid_mouse_move); + usbd_reg_endpoint(dev, HID_RIN_EP, hid_ep_callback); usbd_ep_write(dev, HID_RIN_EP, 0, 0); return usbd_ack; default: @@ -237,8 +346,8 @@ static usbd_respond hid_control (usbd_device *dev, usbd_ctlreq *req, usbd_rqc_ca case USB_HID_SETIDLE: return usbd_ack; case USB_HID_GETREPORT: - dev->status.data_ptr = &hid_report_data; - dev->status.data_count = sizeof(hid_report_data); + dev->status.data_ptr = &hid_report; + dev->status.data_count = sizeof(hid_report); return usbd_ack; default: return usbd_fail; diff --git a/firmware/targets/f6/furi-hal/furi-hal-usb.c b/firmware/targets/f6/furi-hal/furi-hal-usb.c index fc5add2e..5442975f 100644 --- a/firmware/targets/f6/furi-hal/furi-hal-usb.c +++ b/firmware/targets/f6/furi-hal/furi-hal-usb.c @@ -89,6 +89,10 @@ void furi_hal_usb_set_config(UsbMode new_mode) { } } +UsbMode furi_hal_usb_get_config() { + return usb_config.mode_cur; +} + void furi_hal_usb_disable() { if (usb_config.enabled) { susp_evt(&udev, 0, 0); diff --git a/firmware/targets/f7/furi-hal/furi-hal-usb-hid.c b/firmware/targets/f7/furi-hal/furi-hal-usb-hid.c index 38423610..d390c7b5 100644 --- a/firmware/targets/f7/furi-hal/furi-hal-usb-hid.c +++ b/firmware/targets/f7/furi-hal/furi-hal-usb-hid.c @@ -6,10 +6,13 @@ #include "usb_hid.h" #include "hid_usage_desktop.h" #include "hid_usage_button.h" +#include "hid_usage_keyboard.h" #define HID_RIN_EP 0x81 #define HID_RIN_SZ 0x10 +#define HID_KB_MAX_KEYS 6 + struct HidIadDescriptor { struct usb_iad_descriptor hid_iad; struct usb_interface_descriptor hid; @@ -22,31 +25,63 @@ struct HidConfigDescriptor { struct HidIadDescriptor iad_0; } __attribute__((packed)); -/* HID mouse report desscriptor. 2 axis 5 buttons */ +enum HidReportId { + ReportIdKeyboard = 1, + ReportIdMouse = 2, +}; + +/* HID report: keyboard+mouse */ static const uint8_t hid_report_desc[] = { + HID_USAGE_PAGE(HID_PAGE_DESKTOP), + HID_USAGE(HID_DESKTOP_KEYBOARD), + HID_COLLECTION(HID_APPLICATION_COLLECTION), + HID_REPORT_ID(ReportIdKeyboard), + HID_USAGE_PAGE(HID_DESKTOP_KEYPAD), + HID_USAGE_MINIMUM(HID_KEYBOARD_L_CTRL), + HID_USAGE_MAXIMUM(HID_KEYBOARD_R_GUI), + HID_LOGICAL_MINIMUM(0), + HID_LOGICAL_MAXIMUM(1), + HID_REPORT_SIZE(1), + HID_REPORT_COUNT(8), + HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), + HID_REPORT_COUNT(1), + HID_REPORT_SIZE(8), + HID_INPUT(HID_IOF_CONSTANT | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), + HID_REPORT_COUNT(6), + HID_REPORT_SIZE(8), + HID_LOGICAL_MINIMUM(0), + HID_LOGICAL_MAXIMUM(101), + HID_USAGE_PAGE(HID_DESKTOP_KEYPAD), + HID_USAGE_MINIMUM(0), + HID_USAGE_MAXIMUM(101), + HID_INPUT(HID_IOF_DATA | HID_IOF_ARRAY | HID_IOF_ABSOLUTE), + HID_END_COLLECTION, HID_USAGE_PAGE(HID_PAGE_DESKTOP), HID_USAGE(HID_DESKTOP_MOUSE), HID_COLLECTION(HID_APPLICATION_COLLECTION), HID_USAGE(HID_DESKTOP_POINTER), HID_COLLECTION(HID_PHYSICAL_COLLECTION), + HID_REPORT_ID(ReportIdMouse), + HID_USAGE_PAGE(HID_PAGE_BUTTON), + HID_USAGE_MINIMUM(1), + HID_USAGE_MAXIMUM(3), + HID_LOGICAL_MINIMUM(0), + HID_LOGICAL_MAXIMUM(1), + HID_REPORT_COUNT(3), + HID_REPORT_SIZE(1), + HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), + HID_REPORT_SIZE(1), + HID_REPORT_COUNT(5), + HID_INPUT(HID_IOF_CONSTANT | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), + HID_USAGE_PAGE(HID_PAGE_DESKTOP), HID_USAGE(HID_DESKTOP_X), HID_USAGE(HID_DESKTOP_Y), + HID_USAGE(HID_DESKTOP_WHEEL), HID_LOGICAL_MINIMUM(-127), HID_LOGICAL_MAXIMUM(127), HID_REPORT_SIZE(8), - HID_REPORT_COUNT(2), - HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_RELATIVE ), - HID_USAGE_PAGE(HID_PAGE_BUTTON), - HID_USAGE_MINIMUM(1), - HID_USAGE_MAXIMUM(5), - HID_LOGICAL_MINIMUM(0), - HID_LOGICAL_MAXIMUM(1), - HID_REPORT_SIZE(1), - HID_REPORT_COUNT(5), - HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE ), - HID_REPORT_SIZE(1), HID_REPORT_COUNT(3), - HID_INPUT(HID_IOF_CONSTANT), + HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_RELATIVE), HID_END_COLLECTION, HID_END_COLLECTION, }; @@ -122,25 +157,102 @@ static const struct HidConfigDescriptor hid_cfg_desc = { .bEndpointAddress = HID_RIN_EP, .bmAttributes = USB_EPTYPE_INTERRUPT, .wMaxPacketSize = HID_RIN_SZ, - .bInterval = 50, + .bInterval = 10, }, }, }; -static struct { - int8_t x; - int8_t y; - uint8_t buttons; -} __attribute__((packed)) hid_report_data; +struct HidReportMouse { + uint8_t report_id; + uint8_t btn; + int8_t x; + int8_t y; + int8_t wheel; +} __attribute__((packed)); + +struct HidReportKB { + uint8_t report_id; + uint8_t mods; + uint8_t reserved; + uint8_t btn[HID_KB_MAX_KEYS]; +} __attribute__((packed)); + +static struct HidReport { + struct HidReportKB keyboard; + struct HidReportMouse mouse; +} __attribute__((packed)) hid_report; static void hid_init(usbd_device* dev, struct UsbInterface* intf); static void hid_deinit(usbd_device *dev); static void hid_on_wakeup(usbd_device *dev); static void hid_on_suspend(usbd_device *dev); +static bool hid_send_report(uint8_t report_id); static usbd_respond hid_ep_config (usbd_device *dev, uint8_t cfg); static usbd_respond hid_control (usbd_device *dev, usbd_ctlreq *req, usbd_rqc_callback *callback); static usbd_device* usb_dev; +static osSemaphoreId_t hid_semaphore = NULL; +static bool hid_connected = false; + +bool furi_hal_hid_is_connected() { + return hid_connected; +} + +bool furi_hal_hid_kb_press(uint16_t button) { + for (uint8_t key_nb = 0; key_nb < HID_KB_MAX_KEYS; key_nb++) { + if (hid_report.keyboard.btn[key_nb] == 0) { + hid_report.keyboard.btn[key_nb] = button & 0xFF; + break; + } + } + hid_report.keyboard.mods |= (button >> 8); + return hid_send_report(ReportIdKeyboard); +} + +bool furi_hal_hid_kb_release(uint16_t button) { + for (uint8_t key_nb = 0; key_nb < HID_KB_MAX_KEYS; key_nb++) { + if (hid_report.keyboard.btn[key_nb] == (button & 0xFF)) { + hid_report.keyboard.btn[key_nb] = 0; + break; + } + } + hid_report.keyboard.mods &= ~(button >> 8); + return hid_send_report(ReportIdKeyboard); +} + +bool furi_hal_hid_kb_release_all() { + for (uint8_t key_nb = 0; key_nb < HID_KB_MAX_KEYS; key_nb++) { + hid_report.keyboard.btn[key_nb] = 0; + } + hid_report.keyboard.mods = 0; + return hid_send_report(ReportIdKeyboard); +} + +bool furi_hal_hid_mouse_move(int8_t dx, int8_t dy) { + hid_report.mouse.x = dx; + hid_report.mouse.y = dy; + bool state = hid_send_report(ReportIdMouse); + hid_report.mouse.x = 0; + hid_report.mouse.y = 0; + return state; +} + +bool furi_hal_hid_mouse_press(uint8_t button) { + hid_report.mouse.btn |= button; + return hid_send_report(ReportIdMouse); +} + +bool furi_hal_hid_mouse_release(uint8_t button) { + hid_report.mouse.btn &= ~button; + return hid_send_report(ReportIdMouse); +} + +bool furi_hal_hid_mouse_scroll(int8_t delta) { + hid_report.mouse.wheel = delta; + bool state = hid_send_report(ReportIdMouse); + hid_report.mouse.wheel = 0; + return state; +} struct UsbInterface usb_hid = { .init = hid_init, @@ -158,7 +270,11 @@ struct UsbInterface usb_hid = { }; static void hid_init(usbd_device* dev, struct UsbInterface* intf) { + if (hid_semaphore == NULL) + hid_semaphore = osSemaphoreNew(1, 1, NULL); usb_dev = dev; + hid_report.keyboard.report_id = ReportIdKeyboard; + hid_report.mouse.report_id = ReportIdMouse; usbd_reg_config(dev, hid_ep_config); usbd_reg_control(dev, hid_control); @@ -172,41 +288,34 @@ static void hid_deinit(usbd_device *dev) { } static void hid_on_wakeup(usbd_device *dev) { + hid_connected = true; } static void hid_on_suspend(usbd_device *dev) { + if (hid_connected == true) { + hid_connected = false; + osSemaphoreRelease(hid_semaphore); + } } -/* HID mouse IN endpoint callback */ -static void hid_mouse_move(usbd_device *dev, uint8_t event, uint8_t ep) { - static uint8_t t = 0; - if (t < 0x10) { - hid_report_data.x = 1; - hid_report_data.y = 0; - } else if (t < 0x20) { - hid_report_data.x = 1; - hid_report_data.y = 1; - } else if (t < 0x30) { - hid_report_data.x = 0; - hid_report_data.y = 1; - } else if (t < 0x40) { - hid_report_data.x = -1; - hid_report_data.y = 1; - } else if (t < 0x50) { - hid_report_data.x = -1; - hid_report_data.y = 0; - } else if (t < 0x60) { - hid_report_data.x = -1; - hid_report_data.y = -1; - } else if (t < 0x70) { - hid_report_data.x = 0; - hid_report_data.y = -1; - } else { - hid_report_data.x = 1; - hid_report_data.y = -1; +static bool hid_send_report(uint8_t report_id) +{ + if ((hid_semaphore == NULL) || (hid_connected == false)) + return false; + + furi_check(osSemaphoreAcquire(hid_semaphore, osWaitForever) == osOK); + if (hid_connected == true) { + if (report_id == ReportIdKeyboard) + usbd_ep_write(usb_dev, HID_RIN_EP, &hid_report.keyboard, sizeof(hid_report.keyboard)); + else + usbd_ep_write(usb_dev, HID_RIN_EP, &hid_report.mouse, sizeof(hid_report.mouse)); + return true; } - t = (t + 1) & 0x7F; - usbd_ep_write(dev, ep, &hid_report_data, sizeof(hid_report_data)); + return false; +} + +static void hid_ep_callback(usbd_device *dev, uint8_t event, uint8_t ep) { + osSemaphoreRelease(hid_semaphore); } /* Configure endpoints */ @@ -220,7 +329,7 @@ static usbd_respond hid_ep_config (usbd_device *dev, uint8_t cfg) { case 1: /* configuring device */ usbd_ep_config(dev, HID_RIN_EP, USB_EPTYPE_INTERRUPT, HID_RIN_SZ); - usbd_reg_endpoint(dev, HID_RIN_EP, hid_mouse_move); + usbd_reg_endpoint(dev, HID_RIN_EP, hid_ep_callback); usbd_ep_write(dev, HID_RIN_EP, 0, 0); return usbd_ack; default: @@ -237,8 +346,8 @@ static usbd_respond hid_control (usbd_device *dev, usbd_ctlreq *req, usbd_rqc_ca case USB_HID_SETIDLE: return usbd_ack; case USB_HID_GETREPORT: - dev->status.data_ptr = &hid_report_data; - dev->status.data_count = sizeof(hid_report_data); + dev->status.data_ptr = &hid_report; + dev->status.data_count = sizeof(hid_report); return usbd_ack; default: return usbd_fail; diff --git a/firmware/targets/f7/furi-hal/furi-hal-usb.c b/firmware/targets/f7/furi-hal/furi-hal-usb.c index fc5add2e..5442975f 100644 --- a/firmware/targets/f7/furi-hal/furi-hal-usb.c +++ b/firmware/targets/f7/furi-hal/furi-hal-usb.c @@ -89,6 +89,10 @@ void furi_hal_usb_set_config(UsbMode new_mode) { } } +UsbMode furi_hal_usb_get_config() { + return usb_config.mode_cur; +} + void furi_hal_usb_disable() { if (usb_config.enabled) { susp_evt(&udev, 0, 0); diff --git a/firmware/targets/furi-hal-include/furi-hal-usb-hid.h b/firmware/targets/furi-hal-include/furi-hal-usb-hid.h new file mode 100644 index 00000000..eb4a2d9f --- /dev/null +++ b/firmware/targets/furi-hal-include/furi-hal-usb-hid.h @@ -0,0 +1,309 @@ +#pragma once + +/** HID keyboard key codes */ +enum HidKeyboardKeys { + KEY_NONE = 0x00, + KEY_ERROR_ROLLOVER = 0x01, + KEY_POST_FAIL = 0x02, + KEY_ERROR_UNDEFINED = 0x03, + KEY_A = 0x04, + KEY_B = 0x05, + KEY_C = 0x06, + KEY_D = 0x07, + KEY_E = 0x08, + KEY_F = 0x09, + KEY_G = 0x0A, + KEY_H = 0x0B, + KEY_I = 0x0C, + KEY_J = 0x0D, + KEY_K = 0x0E, + KEY_L = 0x0F, + KEY_M = 0x10, + KEY_N = 0x11, + KEY_O = 0x12, + KEY_P = 0x13, + KEY_Q = 0x14, + KEY_R = 0x15, + KEY_S = 0x16, + KEY_T = 0x17, + KEY_U = 0x18, + KEY_V = 0x19, + KEY_W = 0x1A, + KEY_X = 0x1B, + KEY_Y = 0x1C, + KEY_Z = 0x1D, + KEY_1 = 0x1E, + KEY_2 = 0x1F, + KEY_3 = 0x20, + KEY_4 = 0x21, + KEY_5 = 0x22, + KEY_6 = 0x23, + KEY_7 = 0x24, + KEY_8 = 0x25, + KEY_9 = 0x26, + KEY_0 = 0x27, + KEY_ENTER = 0x28, + KEY_ESC = 0x29, + KEY_BACKSPACE = 0x2A, + KEY_TAB = 0x2B, + KEY_SPACE = 0x2C, + KEY_MINUS = 0x2D, + KEY_EQUAL = 0x2E, + KEY_LEFT_BRACE = 0x2F, + KEY_RIGHT_BRACE = 0x30, + KEY_BACKSLASH = 0x31, + KEY_NON_US_NUM = 0x32, + KEY_SEMICOLON = 0x33, + KEY_QUOTE = 0x34, + KEY_TILDE = 0x35, + KEY_COMMA = 0x36, + KEY_PERIOD = 0x37, + KEY_SLASH = 0x38, + KEY_CAPS_LOCK = 0x39, + KEY_F1 = 0x3A, + KEY_F2 = 0x3B, + KEY_F3 = 0x3C, + KEY_F4 = 0x3D, + KEY_F5 = 0x3E, + KEY_F6 = 0x3F, + KEY_F7 = 0x40, + KEY_F8 = 0x41, + KEY_F9 = 0x42, + KEY_F10 = 0x43, + KEY_F11 = 0x44, + KEY_F12 = 0x45, + KEY_PRINT = 0x46, + KEY_SCROLL_LOCK = 0x47, + KEY_PAUSE = 0x48, + KEY_INSERT = 0x49, + KEY_HOME = 0x4A, + KEY_PAGE_UP = 0x4B, + KEY_DELETE = 0x4C, + KEY_END = 0x4D, + KEY_PAGE_DOWN = 0x4E, + KEY_RIGHT_ARROW = 0x4F, + KEY_LEFT_ARROW = 0x50, + KEY_DOWN_ARROW = 0x51, + KEY_UP_ARROW = 0x52, + KEY_NUM_LOCK = 0x53, + KEYPAD_DIVIDE = 0x54, + KEYPAD_MULTIPLY = 0x55, + KEYPAD_SUBTRACT = 0x56, + KEYPAD_ADD = 0x57, + KEYPAD_ENTER = 0x58, + KEYPAD_1 = 0x59, + KEYPAD_2 = 0x5A, + KEYPAD_3 = 0x5B, + KEYPAD_4 = 0x5C, + KEYPAD_5 = 0x5D, + KEYPAD_6 = 0x5E, + KEYPAD_7 = 0x5F, + KEYPAD_8 = 0x60, + KEYPAD_9 = 0x61, + KEYPAD_0 = 0x62, + KEYPAD_DOT = 0x63, + KEY_NON_US = 0x64, + KEY_APPLICATION = 0x65, +}; + +/** HID keyboard modifier keys */ +enum HidKeyboardMods { + KEY_MOD_LEFT_CTRL = (1 << 8), + KEY_MOD_LEFT_SHIFT = (1 << 9), + KEY_MOD_LEFT_ALT = (1 << 10), + KEY_MOD_LEFT_GUI = (1 << 11), + KEY_MOD_RIGHT_CTRL = (1 << 12), + KEY_MOD_RIGHT_SHIFT = (1 << 13), + KEY_MOD_RIGHT_ALT = (1 << 14), + KEY_MOD_RIGHT_GUI = (1 << 15), +}; + +/** ASCII to keycode conversion table */ +static const uint16_t hid_asciimap[] = { + KEY_NONE, // NUL + KEY_NONE, // SOH + KEY_NONE, // STX + KEY_NONE, // ETX + KEY_NONE, // EOT + KEY_NONE, // ENQ + KEY_NONE, // ACK + KEY_NONE, // BEL + KEY_BACKSPACE, // BS Backspace + KEY_TAB, // TAB Tab + KEY_ENTER, // LF Enter + KEY_NONE, // VT + KEY_NONE, // FF + KEY_NONE, // CR + KEY_NONE, // SO + KEY_NONE, // SI + KEY_NONE, // DEL + KEY_NONE, // DC1 + KEY_NONE, // DC2 + KEY_NONE, // DC3 + KEY_NONE, // DC4 + KEY_NONE, // NAK + KEY_NONE, // SYN + KEY_NONE, // ETB + KEY_NONE, // CAN + KEY_NONE, // EM + KEY_NONE, // SUB + KEY_NONE, // ESC + KEY_NONE, // FS + KEY_NONE, // GS + KEY_NONE, // RS + KEY_NONE, // US + KEY_SPACE, // ' ' Space + KEY_1 | KEY_MOD_LEFT_SHIFT, // ! + KEY_QUOTE | KEY_MOD_LEFT_SHIFT, // " + KEY_3 | KEY_MOD_LEFT_SHIFT, // # + KEY_4 | KEY_MOD_LEFT_SHIFT, // $ + KEY_5 | KEY_MOD_LEFT_SHIFT, // % + KEY_7 | KEY_MOD_LEFT_SHIFT, // & + KEY_QUOTE, // ' + KEY_9 | KEY_MOD_LEFT_SHIFT, // ( + KEY_0 | KEY_MOD_LEFT_SHIFT, // ) + KEY_8 | KEY_MOD_LEFT_SHIFT, // * + KEY_EQUAL | KEY_MOD_LEFT_SHIFT, // + + KEY_COMMA, // , + KEY_MINUS, // - + KEY_PERIOD, // . + KEY_SLASH, // / + KEY_0, // 0 + KEY_1, // 1 + KEY_2, // 2 + KEY_3, // 3 + KEY_4, // 4 + KEY_5, // 5 + KEY_6, // 6 + KEY_7, // 7 + KEY_8, // 8 + KEY_9, // 9 + KEY_SEMICOLON | KEY_MOD_LEFT_SHIFT, // : + KEY_SEMICOLON, // ; + KEY_COMMA | KEY_MOD_LEFT_SHIFT, // < + KEY_EQUAL, // = + KEY_PERIOD | KEY_MOD_LEFT_SHIFT, // > + KEY_SLASH | KEY_MOD_LEFT_SHIFT, // ? + KEY_2 | KEY_MOD_LEFT_SHIFT, // @ + KEY_A | KEY_MOD_LEFT_SHIFT, // A + KEY_B | KEY_MOD_LEFT_SHIFT, // B + KEY_C | KEY_MOD_LEFT_SHIFT, // C + KEY_D | KEY_MOD_LEFT_SHIFT, // D + KEY_E | KEY_MOD_LEFT_SHIFT, // E + KEY_F | KEY_MOD_LEFT_SHIFT, // F + KEY_G | KEY_MOD_LEFT_SHIFT, // G + KEY_H | KEY_MOD_LEFT_SHIFT, // H + KEY_I | KEY_MOD_LEFT_SHIFT, // I + KEY_J | KEY_MOD_LEFT_SHIFT, // J + KEY_K | KEY_MOD_LEFT_SHIFT, // K + KEY_L | KEY_MOD_LEFT_SHIFT, // L + KEY_M | KEY_MOD_LEFT_SHIFT, // M + KEY_N | KEY_MOD_LEFT_SHIFT, // N + KEY_O | KEY_MOD_LEFT_SHIFT, // O + KEY_P | KEY_MOD_LEFT_SHIFT, // P + KEY_Q | KEY_MOD_LEFT_SHIFT, // Q + KEY_R | KEY_MOD_LEFT_SHIFT, // R + KEY_S | KEY_MOD_LEFT_SHIFT, // S + KEY_T | KEY_MOD_LEFT_SHIFT, // T + KEY_U | KEY_MOD_LEFT_SHIFT, // U + KEY_V | KEY_MOD_LEFT_SHIFT, // V + KEY_W | KEY_MOD_LEFT_SHIFT, // W + KEY_X | KEY_MOD_LEFT_SHIFT, // X + KEY_Y | KEY_MOD_LEFT_SHIFT, // Y + KEY_Z | KEY_MOD_LEFT_SHIFT, // Z + KEY_LEFT_BRACE, // [ + KEY_BACKSLASH, // bslash + KEY_RIGHT_BRACE, // ] + KEY_6 | KEY_MOD_LEFT_SHIFT, // ^ + KEY_MINUS | KEY_MOD_LEFT_SHIFT, // _ + KEY_TILDE, // ` + KEY_A, // a + KEY_B, // b + KEY_C, // c + KEY_D, // d + KEY_E, // e + KEY_F, // f + KEY_G, // g + KEY_H, // h + KEY_I, // i + KEY_J, // j + KEY_K, // k + KEY_L, // l + KEY_M, // m + KEY_N, // n + KEY_O, // o + KEY_P, // p + KEY_Q, // q + KEY_R, // r + KEY_S, // s + KEY_T, // t + KEY_U, // u + KEY_V, // v + KEY_W, // w + KEY_X, // x + KEY_Y, // y + KEY_Z, // z + KEY_LEFT_BRACE | KEY_MOD_LEFT_SHIFT, // { + KEY_BACKSLASH | KEY_MOD_LEFT_SHIFT, // | + KEY_RIGHT_BRACE | KEY_MOD_LEFT_SHIFT, // } + KEY_TILDE | KEY_MOD_LEFT_SHIFT, // ~ + KEY_NONE, // DEL +}; + +/** ASCII to keycode conversion macro */ +#define HID_ASCII_TO_KEY(x) (((uint8_t)x < 128) ? (hid_asciimap[(uint8_t)x]) : KEY_NONE) + +/** HID mouse buttons */ +enum HidMouseButtons { + HID_MOUSE_BTN_LEFT = (1 << 0), + HID_MOUSE_BTN_RIGHT = (1 << 1), + HID_MOUSE_BTN_WHEEL = (1 << 2), +}; + +/** Get USB HID connection state + * + * @return true / false + */ +bool furi_hal_hid_is_connected(); + +/** Set the following key to pressed state and send HID report + * + * @param button key code + */ +bool furi_hal_hid_kb_press(uint16_t button); + +/** Set the following key to released state and send HID report + * + * @param button key code + */ +bool furi_hal_hid_kb_release(uint16_t button); + +/** Clear all pressed keys and send HID report + * + */ +bool furi_hal_hid_kb_release_all(); + +/** Set mouse movement and send HID report + * + * @param dx x coordinate delta + * @param dy y coordinate delta + */ +bool furi_hal_hid_mouse_move(int8_t dx, int8_t dy); + +/** Set mouse button to pressed state and send HID report + * + * @param button key code + */ +bool furi_hal_hid_mouse_press(uint8_t button); + +/** Set mouse button to released state and send HID report + * + * @param button key code + */ +bool furi_hal_hid_mouse_release(uint8_t button); + +/** Set mouse wheel position and send HID report + * + * @param delta number of scroll steps + */ +bool furi_hal_hid_mouse_scroll(int8_t delta); diff --git a/firmware/targets/furi-hal-include/furi-hal-usb.h b/firmware/targets/furi-hal-include/furi-hal-usb.h index 8e70a597..f0bd9c91 100644 --- a/firmware/targets/furi-hal-include/furi-hal-usb.h +++ b/firmware/targets/furi-hal-include/furi-hal-usb.h @@ -2,6 +2,7 @@ #include "usb.h" +/** USB device modes */ typedef enum { UsbModeNone, UsbModeVcpSingle, @@ -12,10 +13,26 @@ typedef enum { UsbModesNum, } UsbMode; +/** USB device low-level initialization + */ void furi_hal_usb_init(); +/** Set USB device configuration + * + * @param mode new USB device mode + */ void furi_hal_usb_set_config(UsbMode mode); +/** Get USB device configuration + * + * @return current USB device mode + */ +UsbMode furi_hal_usb_get_config(); + +/** Disable USB device + */ void furi_hal_usb_disable(); +/** Enable USB device + */ void furi_hal_usb_enable(); diff --git a/firmware/targets/furi-hal-include/furi-hal.h b/firmware/targets/furi-hal-include/furi-hal.h index 7f344f87..2b682b73 100644 --- a/firmware/targets/furi-hal-include/furi-hal.h +++ b/firmware/targets/furi-hal-include/furi-hal.h @@ -34,6 +34,7 @@ template struct STOP_EXTERNING_ME {}; #include "furi-hal-rfid.h" #include "furi-hal-nfc.h" #include "furi-hal-usb.h" +#include "furi-hal-usb-hid.h" /** Init furi-hal */ void furi_hal_init();