diff --git a/applications/system/hid_app/helpers/ble_hid_ext_profile.c b/applications/system/hid_app/helpers/ble_hid_ext_profile.c new file mode 100644 index 000000000..f1858318e --- /dev/null +++ b/applications/system/hid_app/helpers/ble_hid_ext_profile.c @@ -0,0 +1,36 @@ +#include "ble_hid_ext_profile.h" + +#include + +static FuriHalBleProfileBase* ble_profile_hid_ext_start(FuriHalBleProfileParams profile_params) { + UNUSED(profile_params); + + return ble_profile_hid->start(NULL); +} + +static void ble_profile_hid_ext_stop(FuriHalBleProfileBase* profile) { + ble_profile_hid->stop(profile); +} + +static void + ble_profile_hid_ext_get_config(GapConfig* config, FuriHalBleProfileParams profile_params) { + furi_check(config); + furi_check(profile_params); + BleProfileHidExtParams* hid_ext_profile_params = profile_params; + + // Setup config with basic profile + ble_profile_hid->get_gap_config(config, NULL); + + if(hid_ext_profile_params->name[0] != '\0') { + // Set advertise name (skip first byte which is the ADV type) + strlcpy(config->adv_name + 1, hid_ext_profile_params->name, sizeof(config->adv_name) - 1); + } +} + +static const FuriHalBleProfileTemplate profile_callbacks = { + .start = ble_profile_hid_ext_start, + .stop = ble_profile_hid_ext_stop, + .get_gap_config = ble_profile_hid_ext_get_config, +}; + +const FuriHalBleProfileTemplate* ble_profile_hid_ext = &profile_callbacks; diff --git a/applications/system/hid_app/helpers/ble_hid_ext_profile.h b/applications/system/hid_app/helpers/ble_hid_ext_profile.h new file mode 100644 index 000000000..fc0bdbb89 --- /dev/null +++ b/applications/system/hid_app/helpers/ble_hid_ext_profile.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +/** + * Optional arguments to pass along with profile template as + * FuriHalBleProfileParams for tuning profile behavior + **/ +typedef struct { + char name[FURI_HAL_BT_ADV_NAME_LENGTH]; /**< Full device name */ +} BleProfileHidExtParams; + +/** Hid Keyboard Profile descriptor */ +extern const FuriHalBleProfileTemplate* ble_profile_hid_ext; diff --git a/applications/system/hid_app/hid.c b/applications/system/hid_app/hid.c index e297e0738..00c4ee666 100644 --- a/applications/system/hid_app/hid.c +++ b/applications/system/hid_app/hid.c @@ -4,9 +4,14 @@ #include "views.h" #include #include +#include #define TAG "HidApp" +#define HID_BT_CFG_PATH APP_DATA_PATH(".bt_hid.cfg") +#define HID_BT_CFG_FILE_TYPE "Flipper BT Remote Settings File" +#define HID_BT_CFG_VERSION 1 + bool hid_custom_event_callback(void* context, uint32_t event) { furi_assert(context); Hid* app = context; @@ -33,6 +38,60 @@ void bt_hid_remove_pairing(Hid* app) { furi_hal_bt_start_advertising(); } +static void bt_hid_load_cfg(Hid* app) { + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* fff = flipper_format_file_alloc(storage); + bool loaded = false; + + FuriString* temp_str = furi_string_alloc(); + uint32_t temp_uint = 0; + + do { + if(!flipper_format_file_open_existing(fff, HID_BT_CFG_PATH)) break; + + if(!flipper_format_read_header(fff, temp_str, &temp_uint)) break; + if((strcmp(furi_string_get_cstr(temp_str), HID_BT_CFG_FILE_TYPE) != 0) || + (temp_uint != HID_BT_CFG_VERSION)) + break; + + if(flipper_format_read_string(fff, "name", temp_str)) { + strlcpy( + app->ble_hid_cfg.name, + furi_string_get_cstr(temp_str), + sizeof(app->ble_hid_cfg.name)); + } else { + flipper_format_rewind(fff); + } + + loaded = true; + } while(0); + + furi_string_free(temp_str); + + flipper_format_free(fff); + furi_record_close(RECORD_STORAGE); + + if(!loaded) { + app->ble_hid_cfg.name[0] = '\0'; + } +} + +void bt_hid_save_cfg(Hid* app) { + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* fff = flipper_format_file_alloc(storage); + + if(flipper_format_file_open_always(fff, HID_BT_CFG_PATH)) { + do { + if(!flipper_format_write_header_cstr(fff, HID_BT_CFG_FILE_TYPE, HID_BT_CFG_VERSION)) + break; + if(!flipper_format_write_string_cstr(fff, "name", app->ble_hid_cfg.name)) break; + } while(0); + } + + flipper_format_free(fff); + furi_record_close(RECORD_STORAGE); +} + static void bt_hid_connection_status_changed_callback(BtStatus status, void* context) { furi_assert(context); Hid* hid = context; @@ -89,6 +148,11 @@ Hid* hid_alloc() { app->dialog = dialog_ex_alloc(); view_dispatcher_add_view(app->view_dispatcher, HidViewDialog, dialog_ex_get_view(app->dialog)); + // Text input + app->text_input = text_input_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, HidViewTextInput, text_input_get_view(app->text_input)); + // Popup view app->popup = popup_alloc(); view_dispatcher_add_view(app->view_dispatcher, HidViewPopup, popup_get_view(app->popup)); @@ -177,6 +241,8 @@ void hid_free(Hid* app) { submenu_free(app->submenu); view_dispatcher_remove_view(app->view_dispatcher, HidViewDialog); dialog_ex_free(app->dialog); + view_dispatcher_remove_view(app->view_dispatcher, HidViewTextInput); + text_input_free(app->text_input); view_dispatcher_remove_view(app->view_dispatcher, HidViewPopup); popup_free(app->popup); view_dispatcher_remove_view(app->view_dispatcher, HidViewKeynote); @@ -266,7 +332,9 @@ int32_t hid_ble_app(void* p) { furi_record_close(RECORD_STORAGE); - app->ble_hid_profile = bt_profile_start(app->bt, ble_profile_hid, NULL); + bt_hid_load_cfg(app); + + app->ble_hid_profile = bt_profile_start(app->bt, ble_profile_hid_ext, &app->ble_hid_cfg); furi_check(app->ble_hid_profile); diff --git a/applications/system/hid_app/hid.h b/applications/system/hid_app/hid.h index ac565217a..e314c005c 100644 --- a/applications/system/hid_app/hid.h +++ b/applications/system/hid_app/hid.h @@ -5,7 +5,7 @@ #include #include -#include +#include "helpers/ble_hid_ext_profile.h" #include #include @@ -18,6 +18,7 @@ #include #include #include +#include #include "views/hid_keynote.h" #include "views/hid_keyboard.h" #include "views/hid_numpad.h" @@ -40,6 +41,7 @@ typedef struct Hid Hid; struct Hid { FuriHalBleProfileBase* ble_hid_profile; + BleProfileHidExtParams ble_hid_cfg; Bt* bt; Gui* gui; NotificationApp* notifications; @@ -47,6 +49,7 @@ struct Hid { SceneManager* scene_manager; Submenu* submenu; DialogEx* dialog; + TextInput* text_input; Popup* popup; HidKeynote* hid_keynote; HidKeyboard* hid_keyboard; @@ -64,6 +67,7 @@ struct Hid { }; void bt_hid_remove_pairing(Hid* app); +void bt_hid_save_cfg(Hid* app); void hid_hal_keyboard_press(Hid* instance, uint16_t event); void hid_hal_keyboard_release(Hid* instance, uint16_t event); diff --git a/applications/system/hid_app/scenes/hid_scene_config.h b/applications/system/hid_app/scenes/hid_scene_config.h index d18b15558..2064c65b1 100644 --- a/applications/system/hid_app/scenes/hid_scene_config.h +++ b/applications/system/hid_app/scenes/hid_scene_config.h @@ -1,3 +1,4 @@ ADD_SCENE(hid, start, Start) ADD_SCENE(hid, main, Main) ADD_SCENE(hid, unpair, Unpair) +ADD_SCENE(hid, rename, Rename) diff --git a/applications/system/hid_app/scenes/hid_scene_rename.c b/applications/system/hid_app/scenes/hid_scene_rename.c new file mode 100644 index 000000000..983c7d927 --- /dev/null +++ b/applications/system/hid_app/scenes/hid_scene_rename.c @@ -0,0 +1,82 @@ +#include "../hid.h" +#include "../views.h" +#include "hid_icons.h" + +enum HidSceneRenameEvent { + HidSceneRenameEventTextInput, + HidSceneRenameEventPopup, +}; + +static void hid_scene_rename_text_input_callback(void* context) { + Hid* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, HidSceneRenameEventTextInput); +} + +void hid_scene_rename_popup_callback(void* context) { + Hid* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, HidSceneRenameEventPopup); +} + +void hid_scene_rename_on_enter(void* context) { + Hid* app = context; + + // Rename text input view + text_input_reset(app->text_input); + text_input_set_result_callback( + app->text_input, + hid_scene_rename_text_input_callback, + app, + app->ble_hid_cfg.name, + sizeof(app->ble_hid_cfg.name), + true); + text_input_set_header_text(app->text_input, "Bluetooth Name"); + + // Rename success popup view + popup_set_icon(app->popup, 48, 6, &I_DolphinDone_80x58); + popup_set_header(app->popup, "Done", 14, 15, AlignLeft, AlignTop); + popup_set_timeout(app->popup, 1500); + popup_set_context(app->popup, app); + popup_set_callback(app->popup, hid_scene_rename_popup_callback); + popup_enable_timeout(app->popup); + + view_dispatcher_switch_to_view(app->view_dispatcher, HidViewTextInput); +} + +bool hid_scene_rename_on_event(void* context, SceneManagerEvent event) { + Hid* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + if(event.event == HidSceneRenameEventTextInput) { +#ifdef HID_TRANSPORT_BLE + furi_hal_bt_stop_advertising(); + + app->ble_hid_profile = + bt_profile_start(app->bt, ble_profile_hid_ext, &app->ble_hid_cfg); + furi_check(app->ble_hid_profile); + + furi_hal_bt_start_advertising(); +#endif + + bt_hid_save_cfg(app); + + // Show popup + view_dispatcher_switch_to_view(app->view_dispatcher, HidViewPopup); + + } else if(event.event == HidSceneRenameEventPopup) { + scene_manager_previous_scene(app->scene_manager); + } + } + + return consumed; +} + +void hid_scene_rename_on_exit(void* context) { + Hid* app = context; + + text_input_reset(app->text_input); + popup_reset(app->popup); +} diff --git a/applications/system/hid_app/scenes/hid_scene_start.c b/applications/system/hid_app/scenes/hid_scene_start.c index 61d340eec..167b967e6 100644 --- a/applications/system/hid_app/scenes/hid_scene_start.c +++ b/applications/system/hid_app/scenes/hid_scene_start.c @@ -15,6 +15,7 @@ enum HidSubmenuIndex { HidSubmenuIndexMouseJiggler, HidSubmenuIndexMouseJigglerStealth, HidSubmenuIndexPushToTalk, + HidSubmenuIndexRename, HidSubmenuIndexRemovePairing, }; @@ -81,6 +82,12 @@ void hid_scene_start_on_enter(void* context) { hid_scene_start_submenu_callback, app); #ifdef HID_TRANSPORT_BLE + submenu_add_item( + app->submenu, + "Bluetooth Remote Name", + HidSubmenuIndexRename, + hid_scene_start_submenu_callback, + app); submenu_add_item( app->submenu, "Bluetooth Unpairing", @@ -101,6 +108,8 @@ bool hid_scene_start_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { if(event.event == HidSubmenuIndexRemovePairing) { scene_manager_next_scene(app->scene_manager, HidSceneUnpair); + } else if(event.event == HidSubmenuIndexRename) { + scene_manager_next_scene(app->scene_manager, HidSceneRename); } else { HidView view_id; diff --git a/applications/system/hid_app/views.h b/applications/system/hid_app/views.h index 606a48daf..e27d3ecf0 100644 --- a/applications/system/hid_app/views.h +++ b/applications/system/hid_app/views.h @@ -16,4 +16,5 @@ typedef enum { HidViewPushToTalkHelp, HidViewDialog, HidViewPopup, + HidViewTextInput, } HidView; diff --git a/applications/system/hid_app/views/hid_music_macos.h b/applications/system/hid_app/views/hid_music_macos.h index 9deac32eb..f185276f3 100644 --- a/applications/system/hid_app/views/hid_music_macos.h +++ b/applications/system/hid_app/views/hid_music_macos.h @@ -2,9 +2,10 @@ #include +typedef struct Hid Hid; typedef struct HidMusicMacos HidMusicMacos; -HidMusicMacos* hid_music_macos_alloc(); +HidMusicMacos* hid_music_macos_alloc(Hid* hid); void hid_music_macos_free(HidMusicMacos* hid_music_macos); diff --git a/applications/system/hid_app/views/hid_ptt.c b/applications/system/hid_app/views/hid_ptt.c index 59643abac..58599eb51 100644 --- a/applications/system/hid_app/views/hid_ptt.c +++ b/applications/system/hid_app/views/hid_ptt.c @@ -42,6 +42,7 @@ typedef struct { enum HidPushToTalkAppIndex { HidPushToTalkAppIndexDiscord, HidPushToTalkAppIndexFaceTime, + HidPushToTalkAppIndexGather, HidPushToTalkAppIndexGoogleMeet, HidPushToTalkAppIndexGoogleHangouts, HidPushToTalkAppIndexJamulus, @@ -308,7 +309,6 @@ static void hid_ptt_trigger_mute_jamulus(HidPushToTalk* hid_ptt) { } // webex - static void hid_ptt_trigger_camera_webex(HidPushToTalk* hid_ptt) { hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_V); hid_hal_keyboard_release( @@ -325,6 +325,30 @@ static void hid_ptt_trigger_hand_linux_webex(HidPushToTalk* hid_ptt) { hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_RIGHT_SHIFT | HID_KEYBOARD_R); } +// Gather +static void hid_ptt_trigger_hand_gather(HidPushToTalk* hid_ptt) { + hid_hal_keyboard_press(hid_ptt->hid, HID_KEYBOARD_H); + hid_hal_keyboard_release(hid_ptt->hid, HID_KEYBOARD_H); +} +static void hid_ptt_trigger_camera_macos_gather(HidPushToTalk* hid_ptt) { + hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_V); + hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_V); +} +static void hid_ptt_trigger_mute_macos_gather(HidPushToTalk* hid_ptt) { + hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_A); + hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_A); +} +static void hid_ptt_trigger_camera_linux_gather(HidPushToTalk* hid_ptt) { + hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_V); + hid_hal_keyboard_release( + hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_V); +} +static void hid_ptt_trigger_mute_linux_gather(HidPushToTalk* hid_ptt) { + hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_A); + hid_hal_keyboard_release( + hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_A); +} + static void hid_ptt_menu_callback( void* context, uint32_t osIndex, @@ -359,6 +383,13 @@ static void hid_ptt_menu_callback( model->callback_start_ptt = hid_ptt_trigger_cmd_shift_m; model->callback_stop_ptt = hid_ptt_trigger_cmd_shift_m; break; + case HidPushToTalkAppIndexGather: + model->callback_trigger_mute = hid_ptt_trigger_mute_macos_gather; + model->callback_trigger_camera = hid_ptt_trigger_camera_macos_gather; + model->callback_trigger_hand = hid_ptt_trigger_hand_gather; + model->callback_start_ptt = hid_ptt_trigger_mute_macos_gather; + model->callback_stop_ptt = hid_ptt_trigger_mute_macos_gather; + break; case HidPushToTalkAppIndexGoogleHangouts: model->callback_trigger_mute = hid_ptt_trigger_mute_macos_hangouts; model->callback_trigger_camera = hid_ptt_trigger_camera_macos_hangouts; @@ -434,6 +465,13 @@ static void hid_ptt_menu_callback( model->callback_start_ptt = hid_ptt_start_ptt_linux_discord; model->callback_stop_ptt = hid_ptt_stop_ptt_linux_discord; break; + case HidPushToTalkAppIndexGather: + model->callback_trigger_mute = hid_ptt_trigger_mute_linux_gather; + model->callback_trigger_camera = hid_ptt_trigger_camera_linux_gather; + model->callback_trigger_hand = hid_ptt_trigger_hand_gather; + model->callback_start_ptt = hid_ptt_trigger_mute_linux_gather; + model->callback_stop_ptt = hid_ptt_trigger_mute_linux_gather; + break; case HidPushToTalkAppIndexGoogleHangouts: model->callback_trigger_mute = hid_ptt_trigger_mute_linux_hangouts; model->callback_trigger_camera = hid_ptt_trigger_camera_linux_hangouts; @@ -873,6 +911,20 @@ HidPushToTalk* hid_ptt_alloc(Hid* hid) { HidPushToTalkAppIndexFaceTime, hid_ptt_menu_callback, hid_ptt); + ptt_menu_add_item_to_list( + hid->hid_ptt_menu, + HidPushToTalkMacOS, + "Gather", + HidPushToTalkAppIndexGather, + hid_ptt_menu_callback, + hid_ptt); + ptt_menu_add_item_to_list( + hid->hid_ptt_menu, + HidPushToTalkLinux, + "Gather", + HidPushToTalkAppIndexGather, + hid_ptt_menu_callback, + hid_ptt); ptt_menu_add_item_to_list( hid->hid_ptt_menu, HidPushToTalkMacOS, @@ -932,14 +984,14 @@ HidPushToTalk* hid_ptt_alloc(Hid* hid) { ptt_menu_add_item_to_list( hid->hid_ptt_menu, HidPushToTalkMacOS, - "Slack Hubble", + "Slack Huddle", HidPushToTalkAppIndexSlackHubble, hid_ptt_menu_callback, hid_ptt); ptt_menu_add_item_to_list( hid->hid_ptt_menu, HidPushToTalkLinux, - "Slack Hubble", + "Slack Huddle", HidPushToTalkAppIndexSlackHubble, hid_ptt_menu_callback, hid_ptt);