From 6b3625f46b7ac8dff1ea410df25567ca8356b883 Mon Sep 17 00:00:00 2001 From: Michael Marcucci Date: Fri, 8 Jul 2022 08:36:34 -0400 Subject: [PATCH] Bluetooth Remote Additions (#1330) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update the HID Keycodes to pull from the library * Composite BLE Report Map, add consumer & mouse HID * Add Mouse & keyboard bt remote, fixed media remote * BT Keyboard remove long press shift * Fix usb hid modifier keys * Fixed misaligned bad usb keys * Fix keyboard app keys * Partial fix for bt app and linux * Update to work across platforms * Fix for report ids * BtHidApp: move variable from bss to model, cleanup naming. * FuriHal: add const to immutable data declaration Co-authored-by: あく --- applications/bad_usb/bad_usb_script.c | 112 ++--- applications/bt/bt_hid_app/bt_hid.c | 36 +- applications/bt/bt_hid_app/bt_hid.h | 6 + .../bt/bt_hid_app/views/bt_hid_keyboard.c | 384 ++++++++++++++++++ .../bt/bt_hid_app/views/bt_hid_keyboard.h | 13 + .../bt/bt_hid_app/views/bt_hid_keynote.c | 155 +++---- .../bt/bt_hid_app/views/bt_hid_media.c | 36 +- .../bt/bt_hid_app/views/bt_hid_mouse.c | 207 ++++++++++ .../bt/bt_hid_app/views/bt_hid_mouse.h | 13 + .../icons/BLE/BLE_HID/Ble_connected_15x15.png | Bin 0 -> 177 bytes .../BLE/BLE_HID/Ble_disconnected_15x15.png | Bin 0 -> 178 bytes firmware/targets/f7/ble_glue/hid_service.c | 214 ++++++---- firmware/targets/f7/ble_glue/hid_service.h | 15 +- firmware/targets/f7/furi_hal/furi_hal_bt.c | 4 +- .../targets/f7/furi_hal/furi_hal_bt_hid.c | 343 +++++++++------- .../targets/f7/furi_hal/furi_hal_usb_hid.c | 5 - .../furi_hal_include/furi_hal_bt_hid.h | 60 ++- .../furi_hal_include/furi_hal_usb_hid.h | 371 +++++++---------- 18 files changed, 1341 insertions(+), 633 deletions(-) create mode 100644 applications/bt/bt_hid_app/views/bt_hid_keyboard.c create mode 100644 applications/bt/bt_hid_app/views/bt_hid_keyboard.h create mode 100644 applications/bt/bt_hid_app/views/bt_hid_mouse.c create mode 100644 applications/bt/bt_hid_app/views/bt_hid_mouse.h create mode 100644 assets/icons/BLE/BLE_HID/Ble_connected_15x15.png create mode 100644 assets/icons/BLE/BLE_HID/Ble_disconnected_15x15.png diff --git a/applications/bad_usb/bad_usb_script.c b/applications/bad_usb/bad_usb_script.c index 0266b51f..e6c0f893 100644 --- a/applications/bad_usb/bad_usb_script.c +++ b/applications/bad_usb/bad_usb_script.c @@ -57,48 +57,48 @@ static const DuckyKey ducky_keys[] = { {"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}, + {"DOWNARROW", HID_KEYBOARD_DOWN_ARROW}, + {"DOWN", HID_KEYBOARD_DOWN_ARROW}, + {"LEFTARROW", HID_KEYBOARD_LEFT_ARROW}, + {"LEFT", HID_KEYBOARD_LEFT_ARROW}, + {"RIGHTARROW", HID_KEYBOARD_RIGHT_ARROW}, + {"RIGHT", HID_KEYBOARD_RIGHT_ARROW}, + {"UPARROW", HID_KEYBOARD_UP_ARROW}, + {"UP", HID_KEYBOARD_UP_ARROW}, - {"ENTER", 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}, + {"ENTER", HID_KEYBOARD_RETURN}, + {"BREAK", HID_KEYBOARD_PAUSE}, + {"PAUSE", HID_KEYBOARD_PAUSE}, + {"CAPSLOCK", HID_KEYBOARD_CAPS_LOCK}, + {"DELETE", HID_KEYBOARD_DELETE}, + {"BACKSPACE", HID_KEYPAD_BACKSPACE}, + {"END", HID_KEYBOARD_END}, + {"ESC", HID_KEYBOARD_ESCAPE}, + {"ESCAPE", HID_KEYBOARD_ESCAPE}, + {"HOME", HID_KEYBOARD_HOME}, + {"INSERT", HID_KEYBOARD_INSERT}, + {"NUMLOCK", HID_KEYPAD_NUMLOCK}, + {"PAGEUP", HID_KEYBOARD_PAGE_UP}, + {"PAGEDOWN", HID_KEYBOARD_PAGE_DOWN}, + {"PRINTSCREEN", HID_KEYBOARD_PRINT_SCREEN}, + {"SCROLLOCK", HID_KEYBOARD_SCROLL_LOCK}, + {"SPACE", HID_KEYBOARD_SPACEBAR}, + {"TAB", HID_KEYBOARD_TAB}, + {"MENU", HID_KEYBOARD_APPLICATION}, + {"APP", HID_KEYBOARD_APPLICATION}, - {"F1", KEY_F1}, - {"F2", KEY_F2}, - {"F3", KEY_F3}, - {"F4", KEY_F4}, - {"F5", KEY_F5}, - {"F6", KEY_F6}, - {"F7", KEY_F7}, - {"F8", KEY_F8}, - {"F9", KEY_F9}, - {"F10", KEY_F10}, - {"F11", KEY_F11}, - {"F12", KEY_F12}, + {"F1", HID_KEYBOARD_F1}, + {"F2", HID_KEYBOARD_F2}, + {"F3", HID_KEYBOARD_F3}, + {"F4", HID_KEYBOARD_F4}, + {"F5", HID_KEYBOARD_F5}, + {"F6", HID_KEYBOARD_F6}, + {"F7", HID_KEYBOARD_F7}, + {"F8", HID_KEYBOARD_F8}, + {"F9", HID_KEYBOARD_F9}, + {"F10", HID_KEYBOARD_F10}, + {"F11", HID_KEYBOARD_F11}, + {"F12", HID_KEYBOARD_F12}, }; static const char ducky_cmd_comment[] = {"REM"}; @@ -114,16 +114,16 @@ static const char ducky_cmd_altstr_1[] = {"ALTSTRING "}; static const char ducky_cmd_altstr_2[] = {"ALTCODE "}; static const uint8_t numpad_keys[10] = { - KEYPAD_0, - KEYPAD_1, - KEYPAD_2, - KEYPAD_3, - KEYPAD_4, - KEYPAD_5, - KEYPAD_6, - KEYPAD_7, - KEYPAD_8, - KEYPAD_9, + HID_KEYPAD_0, + HID_KEYPAD_1, + HID_KEYPAD_2, + HID_KEYPAD_3, + HID_KEYPAD_4, + HID_KEYPAD_5, + HID_KEYPAD_6, + HID_KEYPAD_7, + HID_KEYPAD_8, + HID_KEYPAD_9, }; static bool ducky_get_number(const char* param, uint32_t* val) { @@ -149,8 +149,8 @@ static bool ducky_is_line_end(const char chr) { static void ducky_numlock_on() { if((furi_hal_hid_get_led_state() & HID_KB_LED_NUM) == 0) { - furi_hal_hid_kb_press(KEY_NUM_LOCK); - furi_hal_hid_kb_release(KEY_NUM_LOCK); + furi_hal_hid_kb_press(HID_KEYBOARD_LOCK_NUM_LOCK); + furi_hal_hid_kb_release(HID_KEYBOARD_LOCK_NUM_LOCK); } } @@ -170,7 +170,7 @@ static bool ducky_altchar(const char* charcode) { FURI_LOG_I(WORKER_TAG, "char %s", charcode); - furi_hal_hid_kb_press(KEY_MOD_LEFT_ALT); + furi_hal_hid_kb_press(HID_KEYBOARD_L_ALT); while(!ducky_is_line_end(charcode[i])) { state = ducky_numpad_press(charcode[i]); @@ -178,7 +178,7 @@ static bool ducky_altchar(const char* charcode) { i++; } - furi_hal_hid_kb_release(KEY_MOD_LEFT_ALT); + furi_hal_hid_kb_release(HID_KEYBOARD_L_ALT); return state; } @@ -206,7 +206,7 @@ static bool ducky_string(const char* param) { uint32_t i = 0; while(param[i] != '\0') { uint16_t keycode = HID_ASCII_TO_KEY(param[i]); - if(keycode != KEY_NONE) { + if(keycode != HID_KEYBOARD_NONE) { furi_hal_hid_kb_press(keycode); furi_hal_hid_kb_release(keycode); } @@ -294,7 +294,7 @@ static int32_t ducky_parse_line(BadUsbScript* bad_usb, string_t line) { } else { // Special keys + modifiers uint16_t key = ducky_get_keycode(line_tmp, false); - if(key == KEY_NONE) return SCRIPT_STATE_ERROR; + if(key == HID_KEYBOARD_NONE) return SCRIPT_STATE_ERROR; if((key & 0xFF00) != 0) { // It's a modifier key line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; diff --git a/applications/bt/bt_hid_app/bt_hid.c b/applications/bt/bt_hid_app/bt_hid.c index 4b1037cd..47ee2268 100755 --- a/applications/bt/bt_hid_app/bt_hid.c +++ b/applications/bt/bt_hid_app/bt_hid.c @@ -6,7 +6,9 @@ enum BtDebugSubmenuIndex { BtHidSubmenuIndexKeynote, + BtHidSubmenuIndexKeyboard, BtHidSubmenuIndexMedia, + BtHidSubmenuIndexMouse, }; void bt_hid_submenu_callback(void* context, uint32_t index) { @@ -15,9 +17,15 @@ void bt_hid_submenu_callback(void* context, uint32_t index) { if(index == BtHidSubmenuIndexKeynote) { app->view_id = BtHidViewKeynote; view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewKeynote); + } else if(index == BtHidSubmenuIndexKeyboard) { + app->view_id = BtHidViewKeyboard; + view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewKeyboard); } else if(index == BtHidSubmenuIndexMedia) { app->view_id = BtHidViewMedia; view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewMedia); + } else if(index == BtHidSubmenuIndexMouse) { + app->view_id = BtHidViewMouse; + view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewMouse); } } @@ -25,10 +33,11 @@ void bt_hid_dialog_callback(DialogExResult result, void* context) { furi_assert(context); BtHid* app = context; if(result == DialogExResultLeft) { - // TODO switch to Submenu after Media is done view_dispatcher_stop(app->view_dispatcher); } else if(result == DialogExResultRight) { - view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewKeynote); + view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id); // Show last view + } else if(result == DialogExResultCenter) { + view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewSubmenu); } } @@ -52,7 +61,9 @@ void bt_hid_connection_status_changed_callback(BtStatus status, void* context) { notification_internal_message(bt_hid->notifications, &sequence_reset_blue); } bt_hid_keynote_set_connected_status(bt_hid->bt_hid_keynote, connected); + bt_hid_keyboard_set_connected_status(bt_hid->bt_hid_keyboard, connected); bt_hid_media_set_connected_status(bt_hid->bt_hid_media, connected); + bt_hid_mouse_set_connected_status(bt_hid->bt_hid_mouse, connected); } BtHid* bt_hid_app_alloc() { @@ -76,8 +87,11 @@ BtHid* bt_hid_app_alloc() { app->submenu = submenu_alloc(); submenu_add_item( app->submenu, "Keynote", BtHidSubmenuIndexKeynote, bt_hid_submenu_callback, app); + submenu_add_item( + app->submenu, "Keyboard", BtHidSubmenuIndexKeyboard, bt_hid_submenu_callback, app); submenu_add_item( app->submenu, "Media player", BtHidSubmenuIndexMedia, bt_hid_submenu_callback, app); + submenu_add_item(app->submenu, "Mouse", BtHidSubmenuIndexMouse, bt_hid_submenu_callback, app); view_set_previous_callback(submenu_get_view(app->submenu), bt_hid_exit); view_dispatcher_add_view( app->view_dispatcher, BtHidViewSubmenu, submenu_get_view(app->submenu)); @@ -88,6 +102,7 @@ BtHid* bt_hid_app_alloc() { dialog_ex_set_context(app->dialog, app); dialog_ex_set_left_button_text(app->dialog, "Exit"); dialog_ex_set_right_button_text(app->dialog, "Stay"); + dialog_ex_set_center_button_text(app->dialog, "Menu"); dialog_ex_set_header(app->dialog, "Close current app?", 16, 12, AlignLeft, AlignTop); view_dispatcher_add_view( app->view_dispatcher, BtHidViewExitConfirm, dialog_ex_get_view(app->dialog)); @@ -99,12 +114,25 @@ BtHid* bt_hid_app_alloc() { view_dispatcher_add_view( app->view_dispatcher, BtHidViewKeynote, bt_hid_keynote_get_view(app->bt_hid_keynote)); + // Keyboard view + app->bt_hid_keyboard = bt_hid_keyboard_alloc(); + view_set_previous_callback( + bt_hid_keyboard_get_view(app->bt_hid_keyboard), bt_hid_exit_confirm_view); + view_dispatcher_add_view( + app->view_dispatcher, BtHidViewKeyboard, bt_hid_keyboard_get_view(app->bt_hid_keyboard)); + // Media view app->bt_hid_media = bt_hid_media_alloc(); view_set_previous_callback(bt_hid_media_get_view(app->bt_hid_media), bt_hid_exit_confirm_view); view_dispatcher_add_view( app->view_dispatcher, BtHidViewMedia, bt_hid_media_get_view(app->bt_hid_media)); + // Mouse view + app->bt_hid_mouse = bt_hid_mouse_alloc(); + view_set_previous_callback(bt_hid_mouse_get_view(app->bt_hid_mouse), bt_hid_exit_confirm_view); + view_dispatcher_add_view( + app->view_dispatcher, BtHidViewMouse, bt_hid_mouse_get_view(app->bt_hid_mouse)); + // TODO switch to menu after Media is done view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewKeynote); @@ -124,8 +152,12 @@ void bt_hid_app_free(BtHid* app) { dialog_ex_free(app->dialog); view_dispatcher_remove_view(app->view_dispatcher, BtHidViewKeynote); bt_hid_keynote_free(app->bt_hid_keynote); + view_dispatcher_remove_view(app->view_dispatcher, BtHidViewKeyboard); + bt_hid_keyboard_free(app->bt_hid_keyboard); view_dispatcher_remove_view(app->view_dispatcher, BtHidViewMedia); bt_hid_media_free(app->bt_hid_media); + view_dispatcher_remove_view(app->view_dispatcher, BtHidViewMouse); + bt_hid_mouse_free(app->bt_hid_mouse); view_dispatcher_free(app->view_dispatcher); // Close records diff --git a/applications/bt/bt_hid_app/bt_hid.h b/applications/bt/bt_hid_app/bt_hid.h index 875cac58..81d092db 100644 --- a/applications/bt/bt_hid_app/bt_hid.h +++ b/applications/bt/bt_hid_app/bt_hid.h @@ -10,7 +10,9 @@ #include #include #include "views/bt_hid_keynote.h" +#include "views/bt_hid_keyboard.h" #include "views/bt_hid_media.h" +#include "views/bt_hid_mouse.h" typedef struct { Bt* bt; @@ -20,13 +22,17 @@ typedef struct { Submenu* submenu; DialogEx* dialog; BtHidKeynote* bt_hid_keynote; + BtHidKeyboard* bt_hid_keyboard; BtHidMedia* bt_hid_media; + BtHidMouse* bt_hid_mouse; uint32_t view_id; } BtHid; typedef enum { BtHidViewSubmenu, BtHidViewKeynote, + BtHidViewKeyboard, BtHidViewMedia, + BtHidViewMouse, BtHidViewExitConfirm, } BtHidView; diff --git a/applications/bt/bt_hid_app/views/bt_hid_keyboard.c b/applications/bt/bt_hid_app/views/bt_hid_keyboard.c new file mode 100644 index 00000000..1088e295 --- /dev/null +++ b/applications/bt/bt_hid_app/views/bt_hid_keyboard.c @@ -0,0 +1,384 @@ +#include "bt_hid_keyboard.h" +#include +#include +#include +#include +#include + +struct BtHidKeyboard { + View* view; +}; + +typedef struct { + bool shift; + bool alt; + bool ctrl; + bool gui; + uint8_t x; + uint8_t y; + uint8_t last_key_code; + uint16_t modifier_code; + bool ok_pressed; + bool back_pressed; + bool connected; + char key_string[5]; +} BtHidKeyboardModel; + +typedef struct { + uint8_t width; + char* key; + const Icon* icon; + char* shift_key; + uint8_t value; +} BtHidKeyboardKey; + +typedef struct { + int8_t x; + int8_t y; +} BtHidKeyboardPoint; + +// 4 BY 12 +#define MARGIN_TOP 0 +#define MARGIN_LEFT 4 +#define KEY_WIDTH 9 +#define KEY_HEIGHT 12 +#define KEY_PADDING 1 +#define ROW_COUNT 6 +#define COLUMN_COUNT 12 + +// 0 width items are not drawn, but there value is used +const BtHidKeyboardKey bt_hid_keyboard_keyset[ROW_COUNT][COLUMN_COUNT] = { + { + {.width = 1, .icon = NULL, .key = "1", .shift_key = "!", .value = HID_KEYBOARD_1}, + {.width = 1, .icon = NULL, .key = "2", .shift_key = "@", .value = HID_KEYBOARD_2}, + {.width = 1, .icon = NULL, .key = "3", .shift_key = "#", .value = HID_KEYBOARD_3}, + {.width = 1, .icon = NULL, .key = "4", .shift_key = "$", .value = HID_KEYBOARD_4}, + {.width = 1, .icon = NULL, .key = "5", .shift_key = "%", .value = HID_KEYBOARD_5}, + {.width = 1, .icon = NULL, .key = "6", .shift_key = "^", .value = HID_KEYBOARD_6}, + {.width = 1, .icon = NULL, .key = "7", .shift_key = "&", .value = HID_KEYBOARD_7}, + {.width = 1, .icon = NULL, .key = "8", .shift_key = "*", .value = HID_KEYBOARD_8}, + {.width = 1, .icon = NULL, .key = "9", .shift_key = "(", .value = HID_KEYBOARD_9}, + {.width = 1, .icon = NULL, .key = "0", .shift_key = ")", .value = HID_KEYBOARD_0}, + {.width = 2, .icon = &I_Pin_arrow_left_9x7, .value = HID_KEYBOARD_DELETE}, + {.width = 0, .value = HID_KEYBOARD_DELETE}, + }, + { + {.width = 1, .icon = NULL, .key = "q", .shift_key = "Q", .value = HID_KEYBOARD_Q}, + {.width = 1, .icon = NULL, .key = "w", .shift_key = "W", .value = HID_KEYBOARD_W}, + {.width = 1, .icon = NULL, .key = "e", .shift_key = "E", .value = HID_KEYBOARD_E}, + {.width = 1, .icon = NULL, .key = "r", .shift_key = "R", .value = HID_KEYBOARD_R}, + {.width = 1, .icon = NULL, .key = "t", .shift_key = "T", .value = HID_KEYBOARD_T}, + {.width = 1, .icon = NULL, .key = "y", .shift_key = "Y", .value = HID_KEYBOARD_Y}, + {.width = 1, .icon = NULL, .key = "u", .shift_key = "U", .value = HID_KEYBOARD_U}, + {.width = 1, .icon = NULL, .key = "i", .shift_key = "I", .value = HID_KEYBOARD_I}, + {.width = 1, .icon = NULL, .key = "o", .shift_key = "O", .value = HID_KEYBOARD_O}, + {.width = 1, .icon = NULL, .key = "p", .shift_key = "P", .value = HID_KEYBOARD_P}, + {.width = 1, .icon = NULL, .key = "[", .shift_key = "{", .value = HID_KEYBOARD_OPEN_BRACKET}, + {.width = 1, + .icon = NULL, + .key = "]", + .shift_key = "}", + .value = HID_KEYBOARD_CLOSE_BRACKET}, + }, + { + {.width = 1, .icon = NULL, .key = "a", .shift_key = "A", .value = HID_KEYBOARD_A}, + {.width = 1, .icon = NULL, .key = "s", .shift_key = "S", .value = HID_KEYBOARD_S}, + {.width = 1, .icon = NULL, .key = "d", .shift_key = "D", .value = HID_KEYBOARD_D}, + {.width = 1, .icon = NULL, .key = "f", .shift_key = "F", .value = HID_KEYBOARD_F}, + {.width = 1, .icon = NULL, .key = "g", .shift_key = "G", .value = HID_KEYBOARD_G}, + {.width = 1, .icon = NULL, .key = "h", .shift_key = "H", .value = HID_KEYBOARD_H}, + {.width = 1, .icon = NULL, .key = "j", .shift_key = "J", .value = HID_KEYBOARD_J}, + {.width = 1, .icon = NULL, .key = "k", .shift_key = "K", .value = HID_KEYBOARD_K}, + {.width = 1, .icon = NULL, .key = "l", .shift_key = "L", .value = HID_KEYBOARD_L}, + {.width = 1, .icon = NULL, .key = ";", .shift_key = ":", .value = HID_KEYBOARD_SEMICOLON}, + {.width = 2, .icon = &I_Pin_arrow_right_9x7, .value = HID_KEYBOARD_RETURN}, + {.width = 0, .value = HID_KEYBOARD_RETURN}, + }, + { + {.width = 1, .icon = NULL, .key = "z", .shift_key = "Z", .value = HID_KEYBOARD_Z}, + {.width = 1, .icon = NULL, .key = "x", .shift_key = "X", .value = HID_KEYBOARD_X}, + {.width = 1, .icon = NULL, .key = "c", .shift_key = "C", .value = HID_KEYBOARD_C}, + {.width = 1, .icon = NULL, .key = "v", .shift_key = "V", .value = HID_KEYBOARD_V}, + {.width = 1, .icon = NULL, .key = "b", .shift_key = "B", .value = HID_KEYBOARD_B}, + {.width = 1, .icon = NULL, .key = "n", .shift_key = "N", .value = HID_KEYBOARD_N}, + {.width = 1, .icon = NULL, .key = "m", .shift_key = "M", .value = HID_KEYBOARD_M}, + {.width = 1, .icon = NULL, .key = "/", .shift_key = "?", .value = HID_KEYBOARD_SLASH}, + {.width = 1, .icon = NULL, .key = "\\", .shift_key = "|", .value = HID_KEYBOARD_BACKSLASH}, + {.width = 1, .icon = NULL, .key = "`", .shift_key = "~", .value = HID_KEYBOARD_GRAVE_ACCENT}, + {.width = 1, .icon = &I_ButtonUp_7x4, .value = HID_KEYBOARD_UP_ARROW}, + {.width = 1, .icon = NULL, .key = "-", .shift_key = "_", .value = HID_KEYBOARD_MINUS}, + }, + { + {.width = 1, .icon = &I_Pin_arrow_up7x9, .value = HID_KEYBOARD_L_SHIFT}, + {.width = 1, .icon = NULL, .key = ",", .shift_key = "<", .value = HID_KEYPAD_COMMA}, + {.width = 1, .icon = NULL, .key = ".", .shift_key = ">", .value = HID_KEYBOARD_DOT}, + {.width = 4, .icon = NULL, .key = " ", .value = HID_KEYBOARD_SPACEBAR}, + {.width = 0, .value = HID_KEYBOARD_SPACEBAR}, + {.width = 0, .value = HID_KEYBOARD_SPACEBAR}, + {.width = 0, .value = HID_KEYBOARD_SPACEBAR}, + {.width = 1, .icon = NULL, .key = "'", .shift_key = "\"", .value = HID_KEYBOARD_APOSTROPHE}, + {.width = 1, .icon = NULL, .key = "=", .shift_key = "+", .value = HID_KEYBOARD_EQUAL_SIGN}, + {.width = 1, .icon = &I_ButtonLeft_4x7, .value = HID_KEYBOARD_LEFT_ARROW}, + {.width = 1, .icon = &I_ButtonDown_7x4, .value = HID_KEYBOARD_DOWN_ARROW}, + {.width = 1, .icon = &I_ButtonRight_4x7, .value = HID_KEYBOARD_RIGHT_ARROW}, + }, + { + {.width = 3, .icon = NULL, .key = "Ctrl", .value = HID_KEYBOARD_L_CTRL}, + {.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_CTRL}, + {.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_CTRL}, + {.width = 3, .icon = NULL, .key = "Alt", .value = HID_KEYBOARD_L_ALT}, + {.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_ALT}, + {.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_ALT}, + {.width = 3, .icon = NULL, .key = "Cmd", .value = HID_KEYBOARD_L_GUI}, + {.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_GUI}, + {.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_GUI}, + {.width = 3, .icon = NULL, .key = "Tab", .value = HID_KEYBOARD_TAB}, + {.width = 0, .icon = NULL, .value = HID_KEYBOARD_TAB}, + {.width = 0, .icon = NULL, .value = HID_KEYBOARD_TAB}, + }, +}; + +static void bt_hid_keyboard_to_upper(char* str) { + while(*str) { + *str = toupper((unsigned char)*str); + str++; + } +} + +static void bt_hid_keyboard_draw_key( + Canvas* canvas, + BtHidKeyboardModel* model, + uint8_t x, + uint8_t y, + BtHidKeyboardKey key, + bool selected) { + if(!key.width) return; + + canvas_set_color(canvas, ColorBlack); + uint8_t keyWidth = KEY_WIDTH * key.width + KEY_PADDING * (key.width - 1); + if(selected) { + // Draw a filled box + elements_slightly_rounded_box( + canvas, + MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING), + MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING), + keyWidth, + KEY_HEIGHT); + canvas_set_color(canvas, ColorWhite); + } else { + // Draw a framed box + elements_slightly_rounded_frame( + canvas, + MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING), + MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING), + keyWidth, + KEY_HEIGHT); + } + if(key.icon != NULL) { + // Draw the icon centered on the button + canvas_draw_icon( + canvas, + MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING) + keyWidth / 2 - key.icon->width / 2, + MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING) + KEY_HEIGHT / 2 - key.icon->height / 2, + key.icon); + } else { + // If shift is toggled use the shift key when available + strcpy(model->key_string, (model->shift && key.shift_key != 0) ? key.shift_key : key.key); + // Upper case if ctrl or alt was toggled true + if((model->ctrl && key.value == HID_KEYBOARD_L_CTRL) || + (model->alt && key.value == HID_KEYBOARD_L_ALT) || + (model->gui && key.value == HID_KEYBOARD_L_GUI)) { + bt_hid_keyboard_to_upper(model->key_string); + } + canvas_draw_str_aligned( + canvas, + MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING) + keyWidth / 2 + 1, + MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING) + KEY_HEIGHT / 2, + AlignCenter, + AlignCenter, + model->key_string); + } +} + +static void bt_hid_keyboard_draw_callback(Canvas* canvas, void* context) { + furi_assert(context); + BtHidKeyboardModel* model = context; + + // Header + if(!model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Keyboard"); + elements_multiline_text_aligned( + canvas, 4, 60, AlignLeft, AlignBottom, "Waiting for Connection..."); + return; // Dont render the keyboard if we are not yet connected + } + + canvas_set_font(canvas, FontKeyboard); + // Start shifting the all keys up if on the next row (Scrolling) + uint8_t initY = model->y - 4 > 0 ? model->y - 4 : 0; + for(uint8_t y = initY; y < ROW_COUNT; y++) { + const BtHidKeyboardKey* keyboardKeyRow = bt_hid_keyboard_keyset[y]; + uint8_t x = 0; + for(uint8_t i = 0; i < COLUMN_COUNT; i++) { + BtHidKeyboardKey key = keyboardKeyRow[i]; + // Select when the button is hovered + // Select if the button is hovered within its width + // Select if back is clicked and its the backspace key + // Deselect when the button clicked or not hovered + bool keySelected = (x <= model->x && model->x < (x + key.width)) && y == model->y; + bool backSelected = model->back_pressed && key.value == HID_KEYBOARD_DELETE; + bt_hid_keyboard_draw_key( + canvas, + model, + x, + y - initY, + key, + (!model->ok_pressed && keySelected) || backSelected); + x += key.width; + } + } +} + +static uint8_t bt_hid_keyboard_get_selected_key(BtHidKeyboardModel* model) { + BtHidKeyboardKey key = bt_hid_keyboard_keyset[model->y][model->x]; + // Use upper case if shift is toggled + bool useUppercase = model->shift; + // Check if the key has an upper case version + bool hasUppercase = key.shift_key != 0; + if(useUppercase && hasUppercase) + return key.value; + else + return key.value; +} + +static void bt_hid_keyboard_get_select_key(BtHidKeyboardModel* model, BtHidKeyboardPoint delta) { + // Keep going until a valid spot is found, this allows for nulls and zero width keys in the map + do { + if(((int8_t)model->y) + delta.y < 0) + model->y = ROW_COUNT - 1; + else + model->y = (model->y + delta.y) % ROW_COUNT; + } while(delta.y != 0 && bt_hid_keyboard_keyset[model->y][model->x].value == 0); + + do { + if(((int8_t)model->x) + delta.x < 0) + model->x = COLUMN_COUNT - 1; + else + model->x = (model->x + delta.x) % COLUMN_COUNT; + } while(delta.x != 0 && bt_hid_keyboard_keyset[model->y][model->x].width == + 0); // Skip zero width keys, pretend they are one key +} + +static void bt_hid_keyboard_process(BtHidKeyboard* bt_hid_keyboard, InputEvent* event) { + with_view_model( + bt_hid_keyboard->view, (BtHidKeyboardModel * model) { + if(event->key == InputKeyOk) { + if(event->type == InputTypePress) { + model->ok_pressed = true; + } else if(event->type == InputTypeLong || event->type == InputTypeShort) { + model->last_key_code = bt_hid_keyboard_get_selected_key(model); + + // Toggle the modifier key when clicked, and click the key + if(model->last_key_code == HID_KEYBOARD_L_SHIFT) { + model->shift = !model->shift; + if(model->shift) + model->modifier_code |= KEY_MOD_LEFT_SHIFT; + else + model->modifier_code &= ~KEY_MOD_LEFT_SHIFT; + } else if(model->last_key_code == HID_KEYBOARD_L_ALT) { + model->alt = !model->alt; + if(model->alt) + model->modifier_code |= KEY_MOD_LEFT_ALT; + else + model->modifier_code &= ~KEY_MOD_LEFT_ALT; + } else if(model->last_key_code == HID_KEYBOARD_L_CTRL) { + model->ctrl = !model->ctrl; + if(model->ctrl) + model->modifier_code |= KEY_MOD_LEFT_CTRL; + else + model->modifier_code &= ~KEY_MOD_LEFT_CTRL; + } else if(model->last_key_code == HID_KEYBOARD_L_GUI) { + model->gui = !model->gui; + if(model->gui) + model->modifier_code |= KEY_MOD_LEFT_GUI; + else + model->modifier_code &= ~KEY_MOD_LEFT_GUI; + } + furi_hal_bt_hid_kb_press(model->modifier_code | model->last_key_code); + } else if(event->type == InputTypeRelease) { + // Release happens after short and long presses + furi_hal_bt_hid_kb_release(model->modifier_code | model->last_key_code); + model->ok_pressed = false; + } + } else if(event->key == InputKeyBack) { + // If back is pressed for a short time, backspace + if(event->type == InputTypePress) { + model->back_pressed = true; + } else if(event->type == InputTypeShort) { + furi_hal_bt_hid_kb_press(HID_KEYBOARD_DELETE); + furi_hal_bt_hid_kb_release(HID_KEYBOARD_DELETE); + } else if(event->type == InputTypeRelease) { + model->back_pressed = false; + } + } else if(event->type == InputTypePress || event->type == InputTypeRepeat) { + // Cycle the selected keys + if(event->key == InputKeyUp) { + bt_hid_keyboard_get_select_key(model, (BtHidKeyboardPoint){.x = 0, .y = -1}); + } else if(event->key == InputKeyDown) { + bt_hid_keyboard_get_select_key(model, (BtHidKeyboardPoint){.x = 0, .y = 1}); + } else if(event->key == InputKeyLeft) { + bt_hid_keyboard_get_select_key(model, (BtHidKeyboardPoint){.x = -1, .y = 0}); + } else if(event->key == InputKeyRight) { + bt_hid_keyboard_get_select_key(model, (BtHidKeyboardPoint){.x = 1, .y = 0}); + } + } + return true; + }); +} + +static bool bt_hid_keyboard_input_callback(InputEvent* event, void* context) { + furi_assert(context); + BtHidKeyboard* bt_hid_keyboard = context; + bool consumed = false; + + if(event->type == InputTypeLong && event->key == InputKeyBack) { + furi_hal_bt_hid_kb_release_all(); + } else { + bt_hid_keyboard_process(bt_hid_keyboard, event); + consumed = true; + } + + return consumed; +} + +BtHidKeyboard* bt_hid_keyboard_alloc() { + BtHidKeyboard* bt_hid_keyboard = malloc(sizeof(BtHidKeyboard)); + bt_hid_keyboard->view = view_alloc(); + view_set_context(bt_hid_keyboard->view, bt_hid_keyboard); + view_allocate_model(bt_hid_keyboard->view, ViewModelTypeLocking, sizeof(BtHidKeyboardModel)); + view_set_draw_callback(bt_hid_keyboard->view, bt_hid_keyboard_draw_callback); + view_set_input_callback(bt_hid_keyboard->view, bt_hid_keyboard_input_callback); + + return bt_hid_keyboard; +} + +void bt_hid_keyboard_free(BtHidKeyboard* bt_hid_keyboard) { + furi_assert(bt_hid_keyboard); + view_free(bt_hid_keyboard->view); + free(bt_hid_keyboard); +} + +View* bt_hid_keyboard_get_view(BtHidKeyboard* bt_hid_keyboard) { + furi_assert(bt_hid_keyboard); + return bt_hid_keyboard->view; +} + +void bt_hid_keyboard_set_connected_status(BtHidKeyboard* bt_hid_keyboard, bool connected) { + furi_assert(bt_hid_keyboard); + with_view_model( + bt_hid_keyboard->view, (BtHidKeyboardModel * model) { + model->connected = connected; + return true; + }); +} diff --git a/applications/bt/bt_hid_app/views/bt_hid_keyboard.h b/applications/bt/bt_hid_app/views/bt_hid_keyboard.h new file mode 100644 index 00000000..b2cc928e --- /dev/null +++ b/applications/bt/bt_hid_app/views/bt_hid_keyboard.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +typedef struct BtHidKeyboard BtHidKeyboard; + +BtHidKeyboard* bt_hid_keyboard_alloc(); + +void bt_hid_keyboard_free(BtHidKeyboard* bt_hid_keyboard); + +View* bt_hid_keyboard_get_view(BtHidKeyboard* bt_hid_keyboard); + +void bt_hid_keyboard_set_connected_status(BtHidKeyboard* bt_hid_keyboard, bool connected); diff --git a/applications/bt/bt_hid_app/views/bt_hid_keynote.c b/applications/bt/bt_hid_app/views/bt_hid_keynote.c index c4225227..60a1ebc0 100755 --- a/applications/bt/bt_hid_app/views/bt_hid_keynote.c +++ b/applications/bt/bt_hid_app/views/bt_hid_keynote.c @@ -14,6 +14,7 @@ typedef struct { bool right_pressed; bool down_pressed; bool ok_pressed; + bool back_pressed; bool connected; } BtHidKeynoteModel; @@ -35,106 +36,119 @@ static void bt_hid_keynote_draw_callback(Canvas* canvas, void* context) { BtHidKeynoteModel* model = context; // Header + if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); + } else { + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); + } canvas_set_font(canvas, FontPrimary); - elements_multiline_text_aligned(canvas, 9, 3, AlignLeft, AlignTop, "Keynote"); + elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Keynote"); canvas_set_font(canvas, FontSecondary); - // Connected status - if(model->connected) { - canvas_draw_icon(canvas, 18, 18, &I_Ble_connected_38x34); - elements_multiline_text_aligned(canvas, 9, 60, AlignLeft, AlignBottom, "Connected"); - } else { - canvas_draw_icon(canvas, 18, 18, &I_Ble_disconnected_24x34); - elements_multiline_text_aligned(canvas, 3, 60, AlignLeft, AlignBottom, "Disconnected"); - } - // Up - canvas_draw_icon(canvas, 86, 4, &I_Button_18x18); + canvas_draw_icon(canvas, 21, 24, &I_Button_18x18); if(model->up_pressed) { - elements_slightly_rounded_box(canvas, 89, 6, 13, 13); + elements_slightly_rounded_box(canvas, 24, 26, 13, 13); canvas_set_color(canvas, ColorWhite); } - bt_hid_keynote_draw_arrow(canvas, 95, 10, CanvasDirectionBottomToTop); + bt_hid_keynote_draw_arrow(canvas, 30, 30, CanvasDirectionBottomToTop); canvas_set_color(canvas, ColorBlack); // Down - canvas_draw_icon(canvas, 86, 25, &I_Button_18x18); + canvas_draw_icon(canvas, 21, 45, &I_Button_18x18); if(model->down_pressed) { - elements_slightly_rounded_box(canvas, 89, 27, 13, 13); + elements_slightly_rounded_box(canvas, 24, 47, 13, 13); canvas_set_color(canvas, ColorWhite); } - bt_hid_keynote_draw_arrow(canvas, 95, 35, CanvasDirectionTopToBottom); + bt_hid_keynote_draw_arrow(canvas, 30, 55, CanvasDirectionTopToBottom); canvas_set_color(canvas, ColorBlack); // Left - canvas_draw_icon(canvas, 65, 25, &I_Button_18x18); + canvas_draw_icon(canvas, 0, 45, &I_Button_18x18); if(model->left_pressed) { - elements_slightly_rounded_box(canvas, 68, 27, 13, 13); + elements_slightly_rounded_box(canvas, 3, 47, 13, 13); canvas_set_color(canvas, ColorWhite); } - bt_hid_keynote_draw_arrow(canvas, 72, 33, CanvasDirectionRightToLeft); + bt_hid_keynote_draw_arrow(canvas, 7, 53, CanvasDirectionRightToLeft); canvas_set_color(canvas, ColorBlack); // Right - canvas_draw_icon(canvas, 107, 25, &I_Button_18x18); + canvas_draw_icon(canvas, 42, 45, &I_Button_18x18); if(model->right_pressed) { - elements_slightly_rounded_box(canvas, 110, 27, 13, 13); + elements_slightly_rounded_box(canvas, 45, 47, 13, 13); canvas_set_color(canvas, ColorWhite); } - bt_hid_keynote_draw_arrow(canvas, 118, 33, CanvasDirectionLeftToRight); + bt_hid_keynote_draw_arrow(canvas, 53, 53, CanvasDirectionLeftToRight); canvas_set_color(canvas, ColorBlack); // Ok - canvas_draw_icon(canvas, 63, 45, &I_Space_65x18); + canvas_draw_icon(canvas, 63, 25, &I_Space_65x18); if(model->ok_pressed) { + elements_slightly_rounded_box(canvas, 66, 27, 60, 13); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 74, 29, &I_Ok_btn_9x9); + elements_multiline_text_aligned(canvas, 91, 36, AlignLeft, AlignBottom, "Space"); + canvas_set_color(canvas, ColorBlack); + + // Back + canvas_draw_icon(canvas, 63, 45, &I_Space_65x18); + if(model->back_pressed) { elements_slightly_rounded_box(canvas, 66, 47, 60, 13); canvas_set_color(canvas, ColorWhite); } - canvas_draw_icon(canvas, 74, 49, &I_Ok_btn_9x9); - elements_multiline_text_aligned(canvas, 91, 56, AlignLeft, AlignBottom, "Space"); + canvas_draw_icon(canvas, 110, 49, &I_Ok_btn_9x9); + elements_multiline_text_aligned(canvas, 76, 56, AlignLeft, AlignBottom, "Back"); } -static void bt_hid_keynote_process_press(BtHidKeynote* bt_hid_keynote, InputEvent* event) { +static void bt_hid_keynote_process(BtHidKeynote* bt_hid_keynote, InputEvent* event) { with_view_model( bt_hid_keynote->view, (BtHidKeynoteModel * model) { - if(event->key == InputKeyUp) { - model->up_pressed = true; - furi_hal_bt_hid_kb_press(KEY_UP_ARROW); - } else if(event->key == InputKeyDown) { - model->down_pressed = true; - furi_hal_bt_hid_kb_press(KEY_DOWN_ARROW); - } else if(event->key == InputKeyLeft) { - model->left_pressed = true; - furi_hal_bt_hid_kb_press(KEY_LEFT_ARROW); - } else if(event->key == InputKeyRight) { - model->right_pressed = true; - furi_hal_bt_hid_kb_press(KEY_RIGHT_ARROW); - } else if(event->key == InputKeyOk) { - model->ok_pressed = true; - furi_hal_bt_hid_kb_press(KEY_SPACE); - } - return true; - }); -} - -static void bt_hid_keynote_process_release(BtHidKeynote* bt_hid_keynote, InputEvent* event) { - with_view_model( - bt_hid_keynote->view, (BtHidKeynoteModel * model) { - if(event->key == InputKeyUp) { - model->up_pressed = false; - furi_hal_bt_hid_kb_release(KEY_UP_ARROW); - } else if(event->key == InputKeyDown) { - model->down_pressed = false; - furi_hal_bt_hid_kb_release(KEY_DOWN_ARROW); - } else if(event->key == InputKeyLeft) { - model->left_pressed = false; - furi_hal_bt_hid_kb_release(KEY_LEFT_ARROW); - } else if(event->key == InputKeyRight) { - model->right_pressed = false; - furi_hal_bt_hid_kb_release(KEY_RIGHT_ARROW); - } else if(event->key == InputKeyOk) { - model->ok_pressed = false; - furi_hal_bt_hid_kb_release(KEY_SPACE); + if(event->type == InputTypePress) { + if(event->key == InputKeyUp) { + model->up_pressed = true; + furi_hal_bt_hid_kb_press(HID_KEYBOARD_UP_ARROW); + } else if(event->key == InputKeyDown) { + model->down_pressed = true; + furi_hal_bt_hid_kb_press(HID_KEYBOARD_DOWN_ARROW); + } else if(event->key == InputKeyLeft) { + model->left_pressed = true; + furi_hal_bt_hid_kb_press(HID_KEYBOARD_LEFT_ARROW); + } else if(event->key == InputKeyRight) { + model->right_pressed = true; + furi_hal_bt_hid_kb_press(HID_KEYBOARD_RIGHT_ARROW); + } else if(event->key == InputKeyOk) { + model->ok_pressed = true; + furi_hal_bt_hid_kb_press(HID_KEYBOARD_SPACEBAR); + } else if(event->key == InputKeyBack) { + model->back_pressed = true; + } + } else if(event->type == InputTypeRelease) { + if(event->key == InputKeyUp) { + model->up_pressed = false; + furi_hal_bt_hid_kb_release(HID_KEYBOARD_UP_ARROW); + } else if(event->key == InputKeyDown) { + model->down_pressed = false; + furi_hal_bt_hid_kb_release(HID_KEYBOARD_DOWN_ARROW); + } else if(event->key == InputKeyLeft) { + model->left_pressed = false; + furi_hal_bt_hid_kb_release(HID_KEYBOARD_LEFT_ARROW); + } else if(event->key == InputKeyRight) { + model->right_pressed = false; + furi_hal_bt_hid_kb_release(HID_KEYBOARD_RIGHT_ARROW); + } else if(event->key == InputKeyOk) { + model->ok_pressed = false; + furi_hal_bt_hid_kb_release(HID_KEYBOARD_SPACEBAR); + } else if(event->key == InputKeyBack) { + model->back_pressed = false; + } + } else if(event->type == InputTypeShort) { + if(event->key == InputKeyBack) { + furi_hal_bt_hid_kb_press(HID_KEYBOARD_DELETE); + furi_hal_bt_hid_kb_release(HID_KEYBOARD_DELETE); + furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_AC_BACK); + furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_AC_BACK); + } } return true; }); @@ -145,16 +159,11 @@ static bool bt_hid_keynote_input_callback(InputEvent* event, void* context) { BtHidKeynote* bt_hid_keynote = context; bool consumed = false; - if(event->type == InputTypePress) { - bt_hid_keynote_process_press(bt_hid_keynote, event); + if(event->type == InputTypeLong && event->key == InputKeyBack) { + furi_hal_bt_hid_kb_release_all(); + } else { + bt_hid_keynote_process(bt_hid_keynote, event); consumed = true; - } else if(event->type == InputTypeRelease) { - bt_hid_keynote_process_release(bt_hid_keynote, event); - consumed = true; - } else if(event->type == InputTypeShort) { - if(event->key == InputKeyBack) { - furi_hal_hid_kb_release_all(); - } } return consumed; diff --git a/applications/bt/bt_hid_app/views/bt_hid_media.c b/applications/bt/bt_hid_app/views/bt_hid_media.c index 695ce3c3..b384f47c 100755 --- a/applications/bt/bt_hid_app/views/bt_hid_media.c +++ b/applications/bt/bt_hid_app/views/bt_hid_media.c @@ -35,18 +35,14 @@ static void bt_hid_media_draw_callback(Canvas* canvas, void* context) { BtHidMediaModel* model = context; // Header - canvas_set_font(canvas, FontPrimary); - elements_multiline_text_aligned(canvas, 9, 3, AlignLeft, AlignTop, "Media player"); - canvas_set_font(canvas, FontSecondary); - - // Connected status if(model->connected) { - canvas_draw_icon(canvas, 23, 17, &I_Ble_connected_38x34); - elements_multiline_text_aligned(canvas, 35, 61, AlignCenter, AlignBottom, "Connected"); + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); } else { - canvas_draw_icon(canvas, 23, 17, &I_Ble_disconnected_24x34); - elements_multiline_text_aligned(canvas, 35, 61, AlignCenter, AlignBottom, "Disconnected"); + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); } + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Media"); + canvas_set_font(canvas, FontSecondary); // Keypad circles canvas_draw_icon(canvas, 76, 8, &I_Circles_47x47); @@ -100,19 +96,19 @@ static void bt_hid_media_process_press(BtHidMedia* bt_hid_media, InputEvent* eve bt_hid_media->view, (BtHidMediaModel * model) { if(event->key == InputKeyUp) { model->up_pressed = true; - furi_hal_bt_hid_media_press(FuriHalBtHidMediaVolumeUp); + furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_VOLUME_INCREMENT); } else if(event->key == InputKeyDown) { model->down_pressed = true; - furi_hal_bt_hid_media_press(FuriHalBtHidMediaVolumeDown); + furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_VOLUME_DECREMENT); } else if(event->key == InputKeyLeft) { model->left_pressed = true; - furi_hal_bt_hid_media_press(FuriHalBtHidMediaScanPrevious); + furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_SCAN_PREVIOUS_TRACK); } else if(event->key == InputKeyRight) { model->right_pressed = true; - furi_hal_bt_hid_media_press(FuriHalBtHidMediaScanNext); + furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_SCAN_NEXT_TRACK); } else if(event->key == InputKeyOk) { model->ok_pressed = true; - furi_hal_bt_hid_media_press(FuriHalBtHidMediaPlayPause); + furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_PLAY_PAUSE); } return true; }); @@ -123,19 +119,19 @@ static void bt_hid_media_process_release(BtHidMedia* bt_hid_media, InputEvent* e bt_hid_media->view, (BtHidMediaModel * model) { if(event->key == InputKeyUp) { model->up_pressed = false; - furi_hal_bt_hid_media_release(FuriHalBtHidMediaVolumeUp); + furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_VOLUME_INCREMENT); } else if(event->key == InputKeyDown) { model->down_pressed = false; - furi_hal_bt_hid_media_release(FuriHalBtHidMediaVolumeDown); + furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_VOLUME_DECREMENT); } else if(event->key == InputKeyLeft) { model->left_pressed = false; - furi_hal_bt_hid_media_release(FuriHalBtHidMediaScanPrevious); + furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_SCAN_PREVIOUS_TRACK); } else if(event->key == InputKeyRight) { model->right_pressed = false; - furi_hal_bt_hid_media_release(FuriHalBtHidMediaScanNext); + furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_SCAN_NEXT_TRACK); } else if(event->key == InputKeyOk) { model->ok_pressed = false; - furi_hal_bt_hid_media_release(FuriHalBtHidMediaPlayPause); + furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_PLAY_PAUSE); } return true; }); @@ -154,7 +150,7 @@ static bool bt_hid_media_input_callback(InputEvent* event, void* context) { consumed = true; } else if(event->type == InputTypeShort) { if(event->key == InputKeyBack) { - furi_hal_bt_hid_media_release_all(); + furi_hal_bt_hid_consumer_key_release_all(); } } diff --git a/applications/bt/bt_hid_app/views/bt_hid_mouse.c b/applications/bt/bt_hid_app/views/bt_hid_mouse.c new file mode 100644 index 00000000..fb1537a2 --- /dev/null +++ b/applications/bt/bt_hid_app/views/bt_hid_mouse.c @@ -0,0 +1,207 @@ +#include "bt_hid_mouse.h" +#include +#include +#include +#include + +struct BtHidMouse { + View* view; +}; +#define MOUSE_MOVE_SHORT 5 +#define MOUSE_MOVE_LONG 20 + +typedef struct { + bool left_pressed; + bool up_pressed; + bool right_pressed; + bool down_pressed; + bool left_mouse_pressed; + bool left_mouse_held; + bool right_mouse_pressed; + bool connected; +} BtHidMouseModel; + +static void bt_hid_mouse_draw_callback(Canvas* canvas, void* context) { + furi_assert(context); + BtHidMouseModel* model = context; + + // Header + if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); + } else { + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); + } + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Mouse"); + canvas_set_font(canvas, FontSecondary); + + if(model->left_mouse_held == true) { + elements_multiline_text_aligned(canvas, 0, 60, AlignLeft, AlignBottom, "Selecting..."); + } + + // Keypad circles + canvas_draw_icon(canvas, 64, 8, &I_Circles_47x47); + + // Up + if(model->up_pressed) { + canvas_draw_icon(canvas, 81, 9, &I_Pressed_Button_13x13); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 84, 10, &I_Pin_arrow_up7x9); + canvas_set_color(canvas, ColorBlack); + + // Down + if(model->down_pressed) { + canvas_draw_icon(canvas, 81, 41, &I_Pressed_Button_13x13); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 84, 43, &I_Pin_arrow_down_7x9); + canvas_set_color(canvas, ColorBlack); + + // Left + if(model->left_pressed) { + canvas_draw_icon(canvas, 65, 25, &I_Pressed_Button_13x13); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 67, 28, &I_Pin_arrow_left_9x7); + canvas_set_color(canvas, ColorBlack); + + // Right + if(model->right_pressed) { + canvas_draw_icon(canvas, 97, 25, &I_Pressed_Button_13x13); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 99, 28, &I_Pin_arrow_right_9x7); + canvas_set_color(canvas, ColorBlack); + + // Ok + if(model->left_mouse_pressed) { + canvas_draw_icon(canvas, 81, 25, &I_Pressed_Button_13x13); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 83, 27, &I_Ok_btn_9x9); + canvas_set_color(canvas, ColorBlack); + + // Back + if(model->right_mouse_pressed) { + canvas_draw_icon(canvas, 108, 48, &I_Pressed_Button_13x13); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 110, 50, &I_Ok_btn_9x9); +} + +static void bt_hid_mouse_process(BtHidMouse* bt_hid_mouse, InputEvent* event) { + with_view_model( + bt_hid_mouse->view, (BtHidMouseModel * model) { + if(event->key == InputKeyBack) { + if(event->type == InputTypeShort) { + furi_hal_bt_hid_mouse_press(HID_MOUSE_BTN_RIGHT); + furi_hal_bt_hid_mouse_release(HID_MOUSE_BTN_RIGHT); + } else if(event->type == InputTypePress) { + model->right_mouse_pressed = true; + } else if(event->type == InputTypeRelease) { + model->right_mouse_pressed = false; + } + } else if(event->key == InputKeyOk) { + if(event->type == InputTypeShort) { + // Just release if it was being held before + if(!model->left_mouse_held) furi_hal_bt_hid_mouse_press(HID_MOUSE_BTN_LEFT); + furi_hal_bt_hid_mouse_release(HID_MOUSE_BTN_LEFT); + model->left_mouse_held = false; + } else if(event->type == InputTypeLong) { + furi_hal_bt_hid_mouse_press(HID_MOUSE_BTN_LEFT); + model->left_mouse_held = true; + model->left_mouse_pressed = true; + } else if(event->type == InputTypePress) { + model->left_mouse_pressed = true; + } else if(event->type == InputTypeRelease) { + // Only release if it wasn't a long press + if(!model->left_mouse_held) model->left_mouse_pressed = false; + } + + } else if(event->key == InputKeyRight) { + if(event->type == InputTypePress) { + model->right_pressed = true; + furi_hal_bt_hid_mouse_move(MOUSE_MOVE_SHORT, 0); + } else if(event->type == InputTypeRepeat) { + furi_hal_bt_hid_mouse_move(MOUSE_MOVE_LONG, 0); + } else if(event->type == InputTypeRelease) { + model->right_pressed = false; + } + } else if(event->key == InputKeyLeft) { + if(event->type == InputTypePress) { + model->left_pressed = true; + furi_hal_bt_hid_mouse_move(-MOUSE_MOVE_SHORT, 0); + } else if(event->type == InputTypeRepeat) { + furi_hal_bt_hid_mouse_move(-MOUSE_MOVE_LONG, 0); + } else if(event->type == InputTypeRelease) { + model->left_pressed = false; + } + } else if(event->key == InputKeyDown) { + if(event->type == InputTypePress) { + model->down_pressed = true; + furi_hal_bt_hid_mouse_move(0, MOUSE_MOVE_SHORT); + } else if(event->type == InputTypeRepeat) { + furi_hal_bt_hid_mouse_move(0, MOUSE_MOVE_LONG); + } else if(event->type == InputTypeRelease) { + model->down_pressed = false; + } + } else if(event->key == InputKeyUp) { + if(event->type == InputTypePress) { + model->up_pressed = true; + furi_hal_bt_hid_mouse_move(0, -MOUSE_MOVE_SHORT); + } else if(event->type == InputTypeRepeat) { + furi_hal_bt_hid_mouse_move(0, -MOUSE_MOVE_LONG); + } else if(event->type == InputTypeRelease) { + model->up_pressed = false; + } + } + return true; + }); +} + +static bool bt_hid_mouse_input_callback(InputEvent* event, void* context) { + furi_assert(context); + BtHidMouse* bt_hid_mouse = context; + bool consumed = false; + + if(event->type == InputTypeLong && event->key == InputKeyBack) { + furi_hal_bt_hid_mouse_release_all(); + } else { + bt_hid_mouse_process(bt_hid_mouse, event); + consumed = true; + } + + return consumed; +} + +BtHidMouse* bt_hid_mouse_alloc() { + BtHidMouse* bt_hid_mouse = malloc(sizeof(BtHidMouse)); + bt_hid_mouse->view = view_alloc(); + view_set_context(bt_hid_mouse->view, bt_hid_mouse); + view_allocate_model(bt_hid_mouse->view, ViewModelTypeLocking, sizeof(BtHidMouseModel)); + view_set_draw_callback(bt_hid_mouse->view, bt_hid_mouse_draw_callback); + view_set_input_callback(bt_hid_mouse->view, bt_hid_mouse_input_callback); + + return bt_hid_mouse; +} + +void bt_hid_mouse_free(BtHidMouse* bt_hid_mouse) { + furi_assert(bt_hid_mouse); + view_free(bt_hid_mouse->view); + free(bt_hid_mouse); +} + +View* bt_hid_mouse_get_view(BtHidMouse* bt_hid_mouse) { + furi_assert(bt_hid_mouse); + return bt_hid_mouse->view; +} + +void bt_hid_mouse_set_connected_status(BtHidMouse* bt_hid_mouse, bool connected) { + furi_assert(bt_hid_mouse); + with_view_model( + bt_hid_mouse->view, (BtHidMouseModel * model) { + model->connected = connected; + return true; + }); +} diff --git a/applications/bt/bt_hid_app/views/bt_hid_mouse.h b/applications/bt/bt_hid_app/views/bt_hid_mouse.h new file mode 100644 index 00000000..a82971d7 --- /dev/null +++ b/applications/bt/bt_hid_app/views/bt_hid_mouse.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +typedef struct BtHidMouse BtHidMouse; + +BtHidMouse* bt_hid_mouse_alloc(); + +void bt_hid_mouse_free(BtHidMouse* bt_hid_mouse); + +View* bt_hid_mouse_get_view(BtHidMouse* bt_hid_mouse); + +void bt_hid_mouse_set_connected_status(BtHidMouse* bt_hid_mouse, bool connected); diff --git a/assets/icons/BLE/BLE_HID/Ble_connected_15x15.png b/assets/icons/BLE/BLE_HID/Ble_connected_15x15.png new file mode 100644 index 0000000000000000000000000000000000000000..58776828ed48a1c3a497755800660b79ea3b8137 GIT binary patch literal 177 zcmeAS@N?(olHy`uVBq!ia0vp^{2({Zh|P-#^I!>>dQMXri7U8|T@ zv7L;LDP~#8^={?X^Shd#k?^xXW6s* bch2Oog!)SEiB$UnbOVE@tDnm{r-UW|4(3Jx literal 0 HcmV?d00001 diff --git a/assets/icons/BLE/BLE_HID/Ble_disconnected_15x15.png b/assets/icons/BLE/BLE_HID/Ble_disconnected_15x15.png new file mode 100644 index 0000000000000000000000000000000000000000..bfc1e7d7f187d88125e1e4b4bb2367d5261359b4 GIT binary patch literal 178 zcmeAS@N?(olHy`uVBq!ia0vp^{220QW{FyL_7{J;KY zD!cHpBWd?U7^82mSk>g>lELXLt>-t(#MCds=m!JOCzjqq-I*4xa*eE?TmsDd?!?S> zc)obEnRW9-o?TaSjl7>-ye`fAx` literal 0 HcmV?d00001 diff --git a/firmware/targets/f7/ble_glue/hid_service.c b/firmware/targets/f7/ble_glue/hid_service.c index e30cdcb0..0efe1747 100644 --- a/firmware/targets/f7/ble_glue/hid_service.c +++ b/firmware/targets/f7/ble_glue/hid_service.c @@ -9,10 +9,9 @@ typedef struct { uint16_t svc_handle; uint16_t protocol_mode_char_handle; - uint16_t report_char_handle; - uint16_t report_ref_desc_handle; + uint16_t report_char_handle[HID_SVC_REPORT_COUNT]; + uint16_t report_ref_desc_handle[HID_SVC_REPORT_COUNT]; uint16_t report_map_char_handle; - uint16_t keyboard_boot_char_handle; uint16_t info_char_handle; uint16_t ctrl_point_char_handle; } HIDSvc; @@ -47,12 +46,22 @@ void hid_svc_start() { SVCCTL_RegisterSvcHandler(hid_svc_event_handler); // Add service svc_uuid.Service_UUID_16 = HUMAN_INTERFACE_DEVICE_SERVICE_UUID; - status = - aci_gatt_add_service(UUID_TYPE_16, &svc_uuid, PRIMARY_SERVICE, 30, &hid_svc->svc_handle); + /** + * Add Human Interface Device Service + */ + status = aci_gatt_add_service( + UUID_TYPE_16, + &svc_uuid, + PRIMARY_SERVICE, + 2 + /* protocol mode */ + (4 * HID_SVC_INPUT_REPORT_COUNT) + (3 * HID_SVC_OUTPUT_REPORT_COUNT) + + (3 * HID_SVC_FEATURE_REPORT_COUNT) + 1 + 2 + 2 + + 2, /* Service + Report Map + HID Information + HID Control Point */ + &hid_svc->svc_handle); if(status) { FURI_LOG_E(TAG, "Failed to add HID service: %d", status); } - // Add Protocol mode characterstics + // Add Protocol mode characteristics char_uuid.Char_UUID_16 = PROTOCOL_MODE_CHAR_UUID; status = aci_gatt_add_char( hid_svc->svc_handle, @@ -75,42 +84,120 @@ void hid_svc_start() { if(status) { FURI_LOG_E(TAG, "Failed to update protocol mode characteristic: %d", status); } - // Add Report characterstics - char_uuid.Char_UUID_16 = REPORT_CHAR_UUID; - status = aci_gatt_add_char( - hid_svc->svc_handle, - UUID_TYPE_16, - &char_uuid, - HID_SVC_REPORT_MAX_LEN, - CHAR_PROP_READ | CHAR_PROP_NOTIFY, - ATTR_PERMISSION_NONE, - GATT_DONT_NOTIFY_EVENTS, - 10, - CHAR_VALUE_LEN_VARIABLE, - &hid_svc->report_char_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to add report characteristic: %d", status); - } - // Add Report descriptor - uint8_t desc_val[] = {0x00, 0x01}; - desc_uuid.Char_UUID_16 = REPORT_REFERENCE_DESCRIPTOR_UUID; - status = aci_gatt_add_char_desc( - hid_svc->svc_handle, - hid_svc->report_char_handle, - UUID_TYPE_16, - &desc_uuid, - HID_SVC_REPORT_REF_LEN, - HID_SVC_REPORT_REF_LEN, - desc_val, - ATTR_PERMISSION_NONE, - ATTR_ACCESS_READ_ONLY, - GATT_DONT_NOTIFY_EVENTS, - MIN_ENCRY_KEY_SIZE, - CHAR_VALUE_LEN_CONSTANT, - &hid_svc->report_ref_desc_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to add report reference descriptor: %d", status); + +#if(HID_SVC_REPORT_COUNT != 0) + for(uint8_t i = 0; i < HID_SVC_REPORT_COUNT; i++) { + if(i < HID_SVC_INPUT_REPORT_COUNT) { + uint8_t buf[2] = {i + 1, 1}; // 1 input + char_uuid.Char_UUID_16 = REPORT_CHAR_UUID; + status = aci_gatt_add_char( + hid_svc->svc_handle, + UUID_TYPE_16, + &char_uuid, + HID_SVC_REPORT_MAX_LEN, + CHAR_PROP_READ | CHAR_PROP_NOTIFY, + ATTR_PERMISSION_NONE, + GATT_DONT_NOTIFY_EVENTS, + 10, + CHAR_VALUE_LEN_VARIABLE, + &(hid_svc->report_char_handle[i])); + if(status) { + FURI_LOG_E(TAG, "Failed to add report characteristic: %d", status); + } + + desc_uuid.Char_UUID_16 = REPORT_REFERENCE_DESCRIPTOR_UUID; + status = aci_gatt_add_char_desc( + hid_svc->svc_handle, + hid_svc->report_char_handle[i], + UUID_TYPE_16, + &desc_uuid, + HID_SVC_REPORT_REF_LEN, + HID_SVC_REPORT_REF_LEN, + buf, + ATTR_PERMISSION_NONE, + ATTR_ACCESS_READ_WRITE, + GATT_DONT_NOTIFY_EVENTS, + MIN_ENCRY_KEY_SIZE, + CHAR_VALUE_LEN_CONSTANT, + &(hid_svc->report_ref_desc_handle[i])); + if(status) { + FURI_LOG_E(TAG, "Failed to add report reference descriptor: %d", status); + } + } else if((i - HID_SVC_INPUT_REPORT_COUNT) < HID_SVC_OUTPUT_REPORT_COUNT) { + uint8_t buf[2] = {i + 1, 2}; // 2 output + char_uuid.Char_UUID_16 = REPORT_CHAR_UUID; + status = aci_gatt_add_char( + hid_svc->svc_handle, + UUID_TYPE_16, + &char_uuid, + HID_SVC_REPORT_MAX_LEN, + CHAR_PROP_READ | CHAR_PROP_NOTIFY, + ATTR_PERMISSION_NONE, + GATT_DONT_NOTIFY_EVENTS, + 10, + CHAR_VALUE_LEN_VARIABLE, + &(hid_svc->report_char_handle[i])); + if(status) { + FURI_LOG_E(TAG, "Failed to add report characteristic: %d", status); + } + + desc_uuid.Char_UUID_16 = REPORT_REFERENCE_DESCRIPTOR_UUID; + status = aci_gatt_add_char_desc( + hid_svc->svc_handle, + hid_svc->report_char_handle[i], + UUID_TYPE_16, + &desc_uuid, + HID_SVC_REPORT_REF_LEN, + HID_SVC_REPORT_REF_LEN, + buf, + ATTR_PERMISSION_NONE, + ATTR_ACCESS_READ_WRITE, + GATT_DONT_NOTIFY_EVENTS, + MIN_ENCRY_KEY_SIZE, + CHAR_VALUE_LEN_CONSTANT, + &(hid_svc->report_ref_desc_handle[i])); + if(status) { + FURI_LOG_E(TAG, "Failed to add report reference descriptor: %d", status); + } + } else { + uint8_t buf[2] = {i + 1, 3}; // 3 feature + char_uuid.Char_UUID_16 = REPORT_CHAR_UUID; + status = aci_gatt_add_char( + hid_svc->svc_handle, + UUID_TYPE_16, + &char_uuid, + HID_SVC_REPORT_MAX_LEN, + CHAR_PROP_READ | CHAR_PROP_NOTIFY, + ATTR_PERMISSION_NONE, + GATT_DONT_NOTIFY_EVENTS, + 10, + CHAR_VALUE_LEN_VARIABLE, + &(hid_svc->report_char_handle[i])); + if(status) { + FURI_LOG_E(TAG, "Failed to add report characteristic: %d", status); + } + + desc_uuid.Char_UUID_16 = REPORT_REFERENCE_DESCRIPTOR_UUID; + status = aci_gatt_add_char_desc( + hid_svc->svc_handle, + hid_svc->report_char_handle[i], + UUID_TYPE_16, + &desc_uuid, + HID_SVC_REPORT_REF_LEN, + HID_SVC_REPORT_REF_LEN, + buf, + ATTR_PERMISSION_NONE, + ATTR_ACCESS_READ_WRITE, + GATT_DONT_NOTIFY_EVENTS, + MIN_ENCRY_KEY_SIZE, + CHAR_VALUE_LEN_CONSTANT, + &(hid_svc->report_ref_desc_handle[i])); + if(status) { + FURI_LOG_E(TAG, "Failed to add report reference descriptor: %d", status); + } + } } +#endif // Add Report Map characteristic char_uuid.Char_UUID_16 = REPORT_MAP_CHAR_UUID; status = aci_gatt_add_char( @@ -127,22 +214,7 @@ void hid_svc_start() { if(status) { FURI_LOG_E(TAG, "Failed to add report map characteristic: %d", status); } - // Add Boot Keyboard characteristic - char_uuid.Char_UUID_16 = BOOT_KEYBOARD_INPUT_REPORT_CHAR_UUID; - status = aci_gatt_add_char( - hid_svc->svc_handle, - UUID_TYPE_16, - &char_uuid, - HID_SVC_BOOT_KEYBOARD_INPUT_REPORT_MAX_LEN, - CHAR_PROP_READ | CHAR_PROP_NOTIFY, - ATTR_PERMISSION_NONE, - GATT_NOTIFY_WRITE_REQ_AND_WAIT_FOR_APPL_RESP, - 10, - CHAR_VALUE_LEN_VARIABLE, - &hid_svc->keyboard_boot_char_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to add report map characteristic: %d", status); - } + // Add Information characteristic char_uuid.Char_UUID_16 = HID_INFORMATION_CHAR_UUID; status = aci_gatt_add_char( @@ -177,27 +249,27 @@ void hid_svc_start() { } } -bool hid_svc_update_report_map(uint8_t* data, uint16_t len) { +bool hid_svc_update_report_map(const uint8_t* data, uint16_t len) { furi_assert(data); furi_assert(hid_svc); tBleStatus status = aci_gatt_update_char_value( hid_svc->svc_handle, hid_svc->report_map_char_handle, 0, len, data); if(status) { - FURI_LOG_E(TAG, "Failed updating report map characteristic"); + FURI_LOG_E(TAG, "Failed updating report map characteristic: %d", status); return false; } return true; } -bool hid_svc_update_input_report(uint8_t* data, uint16_t len) { +bool hid_svc_update_input_report(uint8_t input_report_num, uint8_t* data, uint16_t len) { furi_assert(data); furi_assert(hid_svc); - tBleStatus status = - aci_gatt_update_char_value(hid_svc->svc_handle, hid_svc->report_char_handle, 0, len, data); + tBleStatus status = aci_gatt_update_char_value( + hid_svc->svc_handle, hid_svc->report_char_handle[input_report_num], 0, len, data); if(status) { - FURI_LOG_E(TAG, "Failed updating report characteristic"); + FURI_LOG_E(TAG, "Failed updating report characteristic: %d", status); return false; } return true; @@ -210,7 +282,7 @@ bool hid_svc_update_info(uint8_t* data, uint16_t len) { tBleStatus status = aci_gatt_update_char_value(hid_svc->svc_handle, hid_svc->info_char_handle, 0, len, data); if(status) { - FURI_LOG_E(TAG, "Failed updating info characteristic"); + FURI_LOG_E(TAG, "Failed updating info characteristic: %d", status); return false; } return true; @@ -228,18 +300,18 @@ void hid_svc_stop() { if(status) { FURI_LOG_E(TAG, "Failed to delete Report Map characteristic: %d", status); } - status = aci_gatt_del_char(hid_svc->svc_handle, hid_svc->report_char_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to delete Report characteristic: %d", status); +#if(HID_SVC_INPUT_REPORT_COUNT != 0) + for(uint8_t i = 0; i < HID_SVC_REPORT_COUNT; i++) { + status = aci_gatt_del_char(hid_svc->svc_handle, hid_svc->report_char_handle[i]); + if(status) { + FURI_LOG_E(TAG, "Failed to delete Report characteristic: %d", status); + } } +#endif status = aci_gatt_del_char(hid_svc->svc_handle, hid_svc->protocol_mode_char_handle); if(status) { FURI_LOG_E(TAG, "Failed to delete Protocol Mode characteristic: %d", status); } - status = aci_gatt_del_char(hid_svc->svc_handle, hid_svc->keyboard_boot_char_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to delete Keyboard Boot characteristic: %d", status); - } status = aci_gatt_del_char(hid_svc->svc_handle, hid_svc->info_char_handle); if(status) { FURI_LOG_E(TAG, "Failed to delete Information characteristic: %d", status); diff --git a/firmware/targets/f7/ble_glue/hid_service.h b/firmware/targets/f7/ble_glue/hid_service.h index ed1394be..723460d4 100644 --- a/firmware/targets/f7/ble_glue/hid_service.h +++ b/firmware/targets/f7/ble_glue/hid_service.h @@ -3,21 +3,26 @@ #include #include -#define HID_SVC_REPORT_MAP_MAX_LEN (120) -#define HID_SVC_REPORT_MAX_LEN (9) -#define HID_SVC_BOOT_KEYBOARD_INPUT_REPORT_MAX_LEN (8) +#define HID_SVC_REPORT_MAP_MAX_LEN (255) +#define HID_SVC_REPORT_MAX_LEN (255) #define HID_SVC_REPORT_REF_LEN (2) #define HID_SVC_INFO_LEN (4) #define HID_SVC_CONTROL_POINT_LEN (1) +#define HID_SVC_INPUT_REPORT_COUNT (3) +#define HID_SVC_OUTPUT_REPORT_COUNT (0) +#define HID_SVC_FEATURE_REPORT_COUNT (0) +#define HID_SVC_REPORT_COUNT \ + (HID_SVC_INPUT_REPORT_COUNT + HID_SVC_OUTPUT_REPORT_COUNT + HID_SVC_FEATURE_REPORT_COUNT) + void hid_svc_start(); void hid_svc_stop(); bool hid_svc_is_started(); -bool hid_svc_update_report_map(uint8_t* data, uint16_t len); +bool hid_svc_update_report_map(const uint8_t* data, uint16_t len); -bool hid_svc_update_input_report(uint8_t* data, uint16_t len); +bool hid_svc_update_input_report(uint8_t input_report_num, uint8_t* data, uint16_t len); bool hid_svc_update_info(uint8_t* data, uint16_t len); diff --git a/firmware/targets/f7/furi_hal/furi_hal_bt.c b/firmware/targets/f7/furi_hal/furi_hal_bt.c index ad12d773..dde3842d 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_bt.c +++ b/firmware/targets/f7/furi_hal/furi_hal_bt.c @@ -218,8 +218,8 @@ bool furi_hal_bt_start_app(FuriHalBtProfile profile, GapEventCallback event_cb, } else if(profile == FuriHalBtProfileHidKeyboard) { // Change MAC address for HID profile config->mac_address[2]++; - // Change name Flipper -> Keynote - const char* clicker_str = "Keynote"; + // Change name Flipper -> Control + const char* clicker_str = "Control"; memcpy(&config->adv_name[1], clicker_str, strlen(clicker_str)); } if(!gap_init(config, event_cb, context)) { diff --git a/firmware/targets/f7/furi_hal/furi_hal_bt_hid.c b/firmware/targets/f7/furi_hal/furi_hal_bt_hid.c index 507dedfb..22415199 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_bt_hid.c +++ b/firmware/targets/f7/furi_hal/furi_hal_bt_hid.c @@ -1,4 +1,6 @@ #include "furi_hal_bt_hid.h" +#include "furi_hal_usb_hid.h" +#include "usb_hid.h" #include "dev_info_service.h" #include "battery_service.h" #include "hid_service.h" @@ -10,128 +12,118 @@ #define FURI_HAL_BT_HID_INFO_FLAG_REMOTE_WAKE_MSK (0x01) #define FURI_HAL_BT_HID_INFO_FLAG_NORMALLY_CONNECTABLE_MSK (0x02) -#define FURI_HAL_BT_HID_KB_KEYS_MAX (6) +#define FURI_HAL_BT_HID_KB_MAX_KEYS 6 +#define FURI_HAL_BT_HID_CONSUMER_MAX_KEYS 1 -typedef struct { - // uint8_t report_id; - uint8_t mods; - uint8_t reserved; - uint8_t key[FURI_HAL_BT_HID_KB_KEYS_MAX]; -} FuriHalBtHidKbReport; - -typedef struct { - uint8_t report_id; - uint8_t key; -} FuriHalBtHidMediaReport; - -// TODO make composite HID device -static uint8_t furi_hal_bt_hid_report_map_data[] = { - 0x05, - 0x01, // Usage Page (Generic Desktop) - 0x09, - 0x06, // Usage (Keyboard) - 0xA1, - 0x01, // Collection (Application) - // 0x85, 0x01, // Report ID (1) - 0x05, - 0x07, // Usage Page (Key Codes) - 0x19, - 0xe0, // Usage Minimum (224) - 0x29, - 0xe7, // Usage Maximum (231) - 0x15, - 0x00, // Logical Minimum (0) - 0x25, - 0x01, // Logical Maximum (1) - 0x75, - 0x01, // Report Size (1) - 0x95, - 0x08, // Report Count (8) - 0x81, - 0x02, // Input (Data, Variable, Absolute) - - 0x95, - 0x01, // Report Count (1) - 0x75, - 0x08, // Report Size (8) - 0x81, - 0x01, // Input (Constant) reserved byte(1) - - 0x95, - 0x05, // Report Count (5) - 0x75, - 0x01, // Report Size (1) - 0x05, - 0x08, // Usage Page (Page# for LEDs) - 0x19, - 0x01, // Usage Minimum (1) - 0x29, - 0x05, // Usage Maximum (5) - 0x91, - 0x02, // Output (Data, Variable, Absolute), Led report - 0x95, - 0x01, // Report Count (1) - 0x75, - 0x03, // Report Size (3) - 0x91, - 0x01, // Output (Data, Variable, Absolute), Led report padding - - 0x95, - 0x06, // Report Count (6) - 0x75, - 0x08, // Report Size (8) - 0x15, - 0x00, // Logical Minimum (0) - 0x25, - 0x65, // Logical Maximum (101) - 0x05, - 0x07, // Usage Page (Key codes) - 0x19, - 0x00, // Usage Minimum (0) - 0x29, - 0x65, // Usage Maximum (101) - 0x81, - 0x00, // Input (Data, Array) Key array(6 bytes) - - 0x09, - 0x05, // Usage (Vendor Defined) - 0x15, - 0x00, // Logical Minimum (0) - 0x26, - 0xFF, - 0x00, // Logical Maximum (255) - 0x75, - 0x08, // Report Size (8 bit) - 0x95, - 0x02, // Report Count (2) - 0xB1, - 0x02, // Feature (Data, Variable, Absolute) - - 0xC0, // End Collection (Application) - - // 0x05, 0x0C, // Usage Page (Consumer) - // 0x09, 0x01, // Usage (Consumer Control) - // 0xA1, 0x01, // Collection (Application) - // 0x85, 0x02, // Report ID (2) - // 0x05, 0x0C, // Usage Page (Consumer) - // 0x15, 0x00, // Logical Minimum (0) - // 0x25, 0x01, // Logical Maximum (1) - // 0x75, 0x01, // Report Size (1) - // 0x95, 0x07, // Report Count (7) - // 0x09, 0xB5, // Usage (Scan Next Track) - // 0x09, 0xB6, // Usage (Scan Previous Track) - // 0x09, 0xB7, // Usage (Stop) - // 0x09, 0xB8, // Usage (Eject) - // 0x09, 0xCD, // Usage (Play/Pause) - // 0x09, 0xE2, // Usage (Mute) - // 0x09, 0xE9, // Usage (Volume Increment) - // 0x09, 0xEA, // Usage (Volume Decrement) - // 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) - // 0xC0, // End Collection +// Report ids cant be 0 +enum HidReportId { + ReportIdKeyboard = 1, + ReportIdMouse = 2, + ReportIdConsumer = 3, +}; +// Report numbers corresponded to the report id with an offset of 1 +enum HidInputNumber { + ReportNumberKeyboard = 0, + ReportNumberMouse = 1, + ReportNumberConsumer = 2, }; +typedef struct { + uint8_t mods; + uint8_t reserved; + uint8_t key[FURI_HAL_BT_HID_KB_MAX_KEYS]; +} __attribute__((__packed__)) FuriHalBtHidKbReport; + +typedef struct { + uint8_t btn; + int8_t x; + int8_t y; + int8_t wheel; +} __attribute__((__packed__)) FuriHalBtHidMouseReport; + +typedef struct { + uint16_t key[FURI_HAL_BT_HID_CONSUMER_MAX_KEYS]; +} __attribute__((__packed__)) FuriHalBtHidConsumerReport; + +// keyboard+mouse+consumer hid report +static const uint8_t furi_hal_bt_hid_report_map_data[] = { + // Keyboard Report + 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_USAGE_PAGE(HID_PAGE_LED), + HID_REPORT_COUNT(8), + HID_REPORT_SIZE(1), + HID_USAGE_MINIMUM(1), + HID_USAGE_MAXIMUM(8), + HID_OUTPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), + HID_REPORT_COUNT(FURI_HAL_BT_HID_KB_MAX_KEYS), + 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, + // Mouse Report + 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(3), + HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_RELATIVE), + HID_END_COLLECTION, + HID_END_COLLECTION, + // Consumer Report + HID_USAGE_PAGE(HID_PAGE_CONSUMER), + HID_USAGE(HID_CONSUMER_CONTROL), + HID_COLLECTION(HID_APPLICATION_COLLECTION), + HID_REPORT_ID(ReportIdConsumer), + HID_LOGICAL_MINIMUM(0), + HID_RI_LOGICAL_MAXIMUM(16, 0x3FF), + HID_USAGE_MINIMUM(0), + HID_RI_USAGE_MAXIMUM(16, 0x3FF), + HID_REPORT_COUNT(FURI_HAL_BT_HID_CONSUMER_MAX_KEYS), + HID_REPORT_SIZE(16), + HID_INPUT(HID_IOF_DATA | HID_IOF_ARRAY | HID_IOF_ABSOLUTE), + HID_END_COLLECTION, +}; FuriHalBtHidKbReport* kb_report = NULL; -FuriHalBtHidMediaReport* media_report = NULL; +FuriHalBtHidMouseReport* mouse_report = NULL; +FuriHalBtHidConsumerReport* consumer_report = NULL; void furi_hal_bt_hid_start() { // Start device info @@ -148,7 +140,8 @@ void furi_hal_bt_hid_start() { } // Configure HID Keyboard kb_report = malloc(sizeof(FuriHalBtHidKbReport)); - media_report = malloc(sizeof(FuriHalBtHidMediaReport)); + mouse_report = malloc(sizeof(FuriHalBtHidMouseReport)); + consumer_report = malloc(sizeof(FuriHalBtHidConsumerReport)); // Configure Report Map characteristic hid_svc_update_report_map( furi_hal_bt_hid_report_map_data, sizeof(furi_hal_bt_hid_report_map_data)); @@ -165,6 +158,8 @@ void furi_hal_bt_hid_start() { void furi_hal_bt_hid_stop() { furi_assert(kb_report); + furi_assert(mouse_report); + furi_assert(consumer_report); // Stop all services if(dev_info_svc_is_started()) { dev_info_svc_stop(); @@ -176,61 +171,119 @@ void furi_hal_bt_hid_stop() { hid_svc_stop(); } free(kb_report); - free(media_report); - media_report = NULL; + free(mouse_report); + free(consumer_report); kb_report = NULL; + mouse_report = NULL; + consumer_report = NULL; } bool furi_hal_bt_hid_kb_press(uint16_t button) { furi_assert(kb_report); - // kb_report->report_id = 0x01; - for(uint8_t i = 0; i < FURI_HAL_BT_HID_KB_KEYS_MAX; i++) { + for(uint8_t i = 0; i < FURI_HAL_BT_HID_KB_MAX_KEYS; i++) { if(kb_report->key[i] == 0) { kb_report->key[i] = button & 0xFF; break; } } kb_report->mods |= (button >> 8); - return hid_svc_update_input_report((uint8_t*)kb_report, sizeof(FuriHalBtHidKbReport)); + return hid_svc_update_input_report( + ReportNumberKeyboard, (uint8_t*)kb_report, sizeof(FuriHalBtHidKbReport)); } bool furi_hal_bt_hid_kb_release(uint16_t button) { furi_assert(kb_report); - // kb_report->report_id = 0x01; - for(uint8_t i = 0; i < FURI_HAL_BT_HID_KB_KEYS_MAX; i++) { + for(uint8_t i = 0; i < FURI_HAL_BT_HID_KB_MAX_KEYS; i++) { if(kb_report->key[i] == (button & 0xFF)) { kb_report->key[i] = 0; break; } } kb_report->mods &= ~(button >> 8); - return hid_svc_update_input_report((uint8_t*)kb_report, sizeof(FuriHalBtHidKbReport)); + return hid_svc_update_input_report( + ReportNumberKeyboard, (uint8_t*)kb_report, sizeof(FuriHalBtHidKbReport)); } bool furi_hal_bt_hid_kb_release_all() { furi_assert(kb_report); - // kb_report->report_id = 0x01; - memset(kb_report, 0, sizeof(FuriHalBtHidKbReport)); - return hid_svc_update_input_report((uint8_t*)kb_report, sizeof(FuriHalBtHidKbReport)); + for(uint8_t i = 0; i < FURI_HAL_BT_HID_KB_MAX_KEYS; i++) { + kb_report->key[i] = 0; + } + kb_report->mods = 0; + return hid_svc_update_input_report( + ReportNumberKeyboard, (uint8_t*)kb_report, sizeof(FuriHalBtHidKbReport)); } -bool furi_hal_bt_hid_media_press(uint8_t button) { - furi_assert(media_report); - media_report->report_id = 0x02; - media_report->key |= (0x01 << button); - return hid_svc_update_input_report((uint8_t*)media_report, sizeof(FuriHalBtHidMediaReport)); +bool furi_hal_bt_hid_consumer_key_press(uint16_t button) { + furi_assert(consumer_report); + for(uint8_t i = 0; i < FURI_HAL_BT_HID_CONSUMER_MAX_KEYS; i++) { + if(consumer_report->key[i] == 0) { + consumer_report->key[i] = button; + break; + } + } + return hid_svc_update_input_report( + ReportNumberConsumer, (uint8_t*)consumer_report, sizeof(FuriHalBtHidConsumerReport)); } -bool furi_hal_bt_hid_media_release(uint8_t button) { - furi_assert(media_report); - media_report->report_id = 0x02; - media_report->key &= ~(0x01 << button); - return hid_svc_update_input_report((uint8_t*)media_report, sizeof(FuriHalBtHidMediaReport)); +bool furi_hal_bt_hid_consumer_key_release(uint16_t button) { + furi_assert(consumer_report); + for(uint8_t i = 0; i < FURI_HAL_BT_HID_CONSUMER_MAX_KEYS; i++) { + if(consumer_report->key[i] == button) { + consumer_report->key[i] = 0; + break; + } + } + return hid_svc_update_input_report( + ReportNumberConsumer, (uint8_t*)consumer_report, sizeof(FuriHalBtHidConsumerReport)); } -bool furi_hal_bt_hid_media_release_all() { - furi_assert(media_report); - media_report->report_id = 0x02; - media_report->key = 0x00; - return hid_svc_update_input_report((uint8_t*)media_report, sizeof(FuriHalBtHidMediaReport)); +bool furi_hal_bt_hid_consumer_key_release_all() { + furi_assert(consumer_report); + for(uint8_t i = 0; i < FURI_HAL_BT_HID_CONSUMER_MAX_KEYS; i++) { + consumer_report->key[i] = 0; + } + return hid_svc_update_input_report( + ReportNumberConsumer, (uint8_t*)consumer_report, sizeof(FuriHalBtHidConsumerReport)); +} + +bool furi_hal_bt_hid_mouse_move(int8_t dx, int8_t dy) { + furi_assert(mouse_report); + mouse_report->x = dx; + mouse_report->y = dy; + bool state = hid_svc_update_input_report( + ReportNumberMouse, (uint8_t*)mouse_report, sizeof(FuriHalBtHidMouseReport)); + mouse_report->x = 0; + mouse_report->y = 0; + return state; +} + +bool furi_hal_bt_hid_mouse_press(uint8_t button) { + furi_assert(mouse_report); + mouse_report->btn |= button; + return hid_svc_update_input_report( + ReportNumberMouse, (uint8_t*)mouse_report, sizeof(FuriHalBtHidMouseReport)); +} + +bool furi_hal_bt_hid_mouse_release(uint8_t button) { + furi_assert(mouse_report); + mouse_report->btn &= ~button; + return hid_svc_update_input_report( + ReportNumberMouse, (uint8_t*)mouse_report, sizeof(FuriHalBtHidMouseReport)); +} + +bool furi_hal_bt_hid_mouse_release_all() { + furi_assert(mouse_report); + mouse_report->btn = 0; + return hid_svc_update_input_report( + ReportNumberMouse, (uint8_t*)mouse_report, sizeof(FuriHalBtHidMouseReport)); +} + +bool furi_hal_bt_hid_mouse_scroll(int8_t delta) { + furi_assert(mouse_report); + mouse_report->wheel = delta; + bool state = hid_svc_update_input_report( + ReportNumberMouse, (uint8_t*)mouse_report, sizeof(FuriHalBtHidMouseReport)); + mouse_report->wheel = 0; + return state; } 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 aae4dd4f..06f3f231 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_usb_hid.c +++ b/firmware/targets/f7/furi_hal/furi_hal_usb_hid.c @@ -6,11 +6,6 @@ #include "usb.h" #include "usb_hid.h" -#include "hid_usage_desktop.h" -#include "hid_usage_button.h" -#include "hid_usage_keyboard.h" -#include "hid_usage_consumer.h" -#include "hid_usage_led.h" #define HID_EP_IN 0x81 #define HID_EP_OUT 0x01 diff --git a/firmware/targets/furi_hal_include/furi_hal_bt_hid.h b/firmware/targets/furi_hal_include/furi_hal_bt_hid.h index 4faeebae..e35edd01 100644 --- a/firmware/targets/furi_hal_include/furi_hal_bt_hid.h +++ b/firmware/targets/furi_hal_include/furi_hal_bt_hid.h @@ -3,17 +3,6 @@ #include #include -enum FuriHalBtHidMediKeys { - FuriHalBtHidMediaScanNext, - FuriHalBtHidMediaScanPrevious, - FuriHalBtHidMediaStop, - FuriHalBtHidMediaEject, - FuriHalBtHidMediaPlayPause, - FuriHalBtHidMediaMute, - FuriHalBtHidMediaVolumeUp, - FuriHalBtHidMediaVolumeDown, -}; - /** Start Hid Keyboard Profile */ void furi_hal_bt_hid_start(); @@ -44,20 +33,51 @@ bool furi_hal_bt_hid_kb_release(uint16_t button); */ bool furi_hal_bt_hid_kb_release_all(); -/** Release all media buttons +/** Set mouse movement and send HID report * - * @return true on success + * @param dx x coordinate delta + * @param dy y coordinate delta */ -bool furi_hal_bt_hid_media_press(uint8_t button); +bool furi_hal_bt_hid_mouse_move(int8_t dx, int8_t dy); -/** Release all media buttons +/** Set mouse button to pressed state and send HID report * - * @return true on success + * @param button key code */ -bool furi_hal_bt_hid_media_release(uint8_t button); +bool furi_hal_bt_hid_mouse_press(uint8_t button); -/** Release all media buttons +/** Set mouse button to released state and send HID report * - * @return true on success + * @param button key code */ -bool furi_hal_bt_hid_media_release_all(); +bool furi_hal_bt_hid_mouse_release(uint8_t button); + +/** Set mouse button to released state and send HID report + * + * @param button key code + */ +bool furi_hal_bt_hid_mouse_release_all(); + +/** Set mouse wheel position and send HID report + * + * @param delta number of scroll steps + */ +bool furi_hal_bt_hid_mouse_scroll(int8_t delta); + +/** Set the following consumer key to pressed state and send HID report + * + * @param button key code + */ +bool furi_hal_bt_hid_consumer_key_press(uint16_t button); + +/** Set the following consumer key to released state and send HID report + * + * @param button key code + */ +bool furi_hal_bt_hid_consumer_key_release(uint16_t button); + +/** Set consumer key to released state and send HID report + * + * @param button key code + */ +bool furi_hal_bt_hid_consumer_key_release_all(); diff --git a/firmware/targets/furi_hal_include/furi_hal_usb_hid.h b/firmware/targets/furi_hal_include/furi_hal_usb_hid.h index 20c76223..97bac7f0 100644 --- a/firmware/targets/furi_hal_include/furi_hal_usb_hid.h +++ b/firmware/targets/furi_hal_include/furi_hal_usb_hid.h @@ -1,110 +1,13 @@ #pragma once +#include "hid_usage_desktop.h" +#include "hid_usage_button.h" +#include "hid_usage_keyboard.h" +#include "hid_usage_consumer.h" +#include "hid_usage_led.h" -/** 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, -}; +#define HID_KEYBOARD_NONE 0x00 +// Remapping the colon key which is shift + ; to comma +#define HID_KEYBOARD_COMMA HID_KEYBOARD_COLON /** HID keyboard modifier keys */ enum HidKeyboardMods { @@ -120,134 +23,134 @@ enum HidKeyboardMods { /** 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 + HID_KEYBOARD_NONE, // NUL + HID_KEYBOARD_NONE, // SOH + HID_KEYBOARD_NONE, // STX + HID_KEYBOARD_NONE, // ETX + HID_KEYBOARD_NONE, // EOT + HID_KEYBOARD_NONE, // ENQ + HID_KEYBOARD_NONE, // ACK + HID_KEYBOARD_NONE, // BEL + HID_KEYBOARD_DELETE, // BS Backspace + HID_KEYBOARD_TAB, // TAB Tab + HID_KEYBOARD_RETURN, // LF Enter + HID_KEYBOARD_NONE, // VT + HID_KEYBOARD_NONE, // FF + HID_KEYBOARD_NONE, // CR + HID_KEYBOARD_NONE, // SO + HID_KEYBOARD_NONE, // SI + HID_KEYBOARD_NONE, // DEL + HID_KEYBOARD_NONE, // DC1 + HID_KEYBOARD_NONE, // DC2 + HID_KEYBOARD_NONE, // DC3 + HID_KEYBOARD_NONE, // DC4 + HID_KEYBOARD_NONE, // NAK + HID_KEYBOARD_NONE, // SYN + HID_KEYBOARD_NONE, // ETB + HID_KEYBOARD_NONE, // CAN + HID_KEYBOARD_NONE, // EM + HID_KEYBOARD_NONE, // SUB + HID_KEYBOARD_NONE, // ESC + HID_KEYBOARD_NONE, // FS + HID_KEYBOARD_NONE, // GS + HID_KEYBOARD_NONE, // RS + HID_KEYBOARD_NONE, // US + HID_KEYBOARD_SPACEBAR, // ' ' Space + HID_KEYBOARD_1 | KEY_MOD_LEFT_SHIFT, // ! + HID_KEYBOARD_APOSTROPHE | KEY_MOD_LEFT_SHIFT, // " + HID_KEYBOARD_3 | KEY_MOD_LEFT_SHIFT, // # + HID_KEYBOARD_4 | KEY_MOD_LEFT_SHIFT, // $ + HID_KEYBOARD_5 | KEY_MOD_LEFT_SHIFT, // % + HID_KEYBOARD_7 | KEY_MOD_LEFT_SHIFT, // & + HID_KEYBOARD_APOSTROPHE, // ' + HID_KEYBOARD_9 | KEY_MOD_LEFT_SHIFT, // ( + HID_KEYBOARD_0 | KEY_MOD_LEFT_SHIFT, // ) + HID_KEYBOARD_8 | KEY_MOD_LEFT_SHIFT, // * + HID_KEYBOARD_EQUAL_SIGN | KEY_MOD_LEFT_SHIFT, // + + HID_KEYBOARD_COMMA, // , + HID_KEYBOARD_MINUS, // - + HID_KEYBOARD_DOT, // . + HID_KEYBOARD_SLASH, // / + HID_KEYBOARD_0, // 0 + HID_KEYBOARD_1, // 1 + HID_KEYBOARD_2, // 2 + HID_KEYBOARD_3, // 3 + HID_KEYBOARD_4, // 4 + HID_KEYBOARD_5, // 5 + HID_KEYBOARD_6, // 6 + HID_KEYBOARD_7, // 7 + HID_KEYBOARD_8, // 8 + HID_KEYBOARD_9, // 9 + HID_KEYBOARD_SEMICOLON | KEY_MOD_LEFT_SHIFT, // : + HID_KEYBOARD_SEMICOLON, // ; + HID_KEYBOARD_COMMA | KEY_MOD_LEFT_SHIFT, // < + HID_KEYBOARD_EQUAL_SIGN, // = + HID_KEYBOARD_DOT | KEY_MOD_LEFT_SHIFT, // > + HID_KEYBOARD_SLASH | KEY_MOD_LEFT_SHIFT, // ? + HID_KEYBOARD_2 | KEY_MOD_LEFT_SHIFT, // @ + HID_KEYBOARD_A | KEY_MOD_LEFT_SHIFT, // A + HID_KEYBOARD_B | KEY_MOD_LEFT_SHIFT, // B + HID_KEYBOARD_C | KEY_MOD_LEFT_SHIFT, // C + HID_KEYBOARD_D | KEY_MOD_LEFT_SHIFT, // D + HID_KEYBOARD_E | KEY_MOD_LEFT_SHIFT, // E + HID_KEYBOARD_F | KEY_MOD_LEFT_SHIFT, // F + HID_KEYBOARD_G | KEY_MOD_LEFT_SHIFT, // G + HID_KEYBOARD_H | KEY_MOD_LEFT_SHIFT, // H + HID_KEYBOARD_I | KEY_MOD_LEFT_SHIFT, // I + HID_KEYBOARD_J | KEY_MOD_LEFT_SHIFT, // J + HID_KEYBOARD_K | KEY_MOD_LEFT_SHIFT, // K + HID_KEYBOARD_L | KEY_MOD_LEFT_SHIFT, // L + HID_KEYBOARD_M | KEY_MOD_LEFT_SHIFT, // M + HID_KEYBOARD_N | KEY_MOD_LEFT_SHIFT, // N + HID_KEYBOARD_O | KEY_MOD_LEFT_SHIFT, // O + HID_KEYBOARD_P | KEY_MOD_LEFT_SHIFT, // P + HID_KEYBOARD_Q | KEY_MOD_LEFT_SHIFT, // Q + HID_KEYBOARD_R | KEY_MOD_LEFT_SHIFT, // R + HID_KEYBOARD_S | KEY_MOD_LEFT_SHIFT, // S + HID_KEYBOARD_T | KEY_MOD_LEFT_SHIFT, // T + HID_KEYBOARD_U | KEY_MOD_LEFT_SHIFT, // U + HID_KEYBOARD_V | KEY_MOD_LEFT_SHIFT, // V + HID_KEYBOARD_W | KEY_MOD_LEFT_SHIFT, // W + HID_KEYBOARD_X | KEY_MOD_LEFT_SHIFT, // X + HID_KEYBOARD_Y | KEY_MOD_LEFT_SHIFT, // Y + HID_KEYBOARD_Z | KEY_MOD_LEFT_SHIFT, // Z + HID_KEYBOARD_OPEN_BRACKET, // [ + HID_KEYBOARD_BACKSLASH, // bslash + HID_KEYBOARD_CLOSE_BRACKET, // ] + HID_KEYBOARD_6 | KEY_MOD_LEFT_SHIFT, // ^ + HID_KEYBOARD_MINUS | KEY_MOD_LEFT_SHIFT, // _ + HID_KEYBOARD_GRAVE_ACCENT, // ` + HID_KEYBOARD_A, // a + HID_KEYBOARD_B, // b + HID_KEYBOARD_C, // c + HID_KEYBOARD_D, // d + HID_KEYBOARD_E, // e + HID_KEYBOARD_F, // f + HID_KEYBOARD_G, // g + HID_KEYBOARD_H, // h + HID_KEYBOARD_I, // i + HID_KEYBOARD_J, // j + HID_KEYBOARD_K, // k + HID_KEYBOARD_L, // l + HID_KEYBOARD_M, // m + HID_KEYBOARD_N, // n + HID_KEYBOARD_O, // o + HID_KEYBOARD_P, // p + HID_KEYBOARD_Q, // q + HID_KEYBOARD_R, // r + HID_KEYBOARD_S, // s + HID_KEYBOARD_T, // t + HID_KEYBOARD_U, // u + HID_KEYBOARD_V, // v + HID_KEYBOARD_W, // w + HID_KEYBOARD_X, // x + HID_KEYBOARD_Y, // y + HID_KEYBOARD_Z, // z + HID_KEYBOARD_OPEN_BRACKET | KEY_MOD_LEFT_SHIFT, // { + HID_KEYBOARD_BACKSLASH | KEY_MOD_LEFT_SHIFT, // | + HID_KEYBOARD_CLOSE_BRACKET | KEY_MOD_LEFT_SHIFT, // } + HID_KEYBOARD_GRAVE_ACCENT | KEY_MOD_LEFT_SHIFT, // ~ + HID_KEYBOARD_NONE, // DEL }; typedef struct { @@ -260,7 +163,7 @@ typedef struct { typedef void (*HidStateCallback)(bool state, void* context); /** ASCII to keycode conversion macro */ -#define HID_ASCII_TO_KEY(x) (((uint8_t)x < 128) ? (hid_asciimap[(uint8_t)x]) : KEY_NONE) +#define HID_ASCII_TO_KEY(x) (((uint8_t)x < 128) ? (hid_asciimap[(uint8_t)x]) : HID_KEYBOARD_NONE) /** HID keyboard leds */ enum HidKeyboardLeds {